Tips
- 如何控制和同步
React
之外的信息- 移除不必要的
Effect
和Effect Dependencies
- 组件之间共享逻辑-自定义hook
Keywords
代码运行的原因(交互/同步)
, useEffectEvent
, forwardRef + useImperativeHandle, ** flushSync**, ref callback, useSyncExternalStore
Center Paragraph
ref 引用
const xxxRef = useRef() xxxRef
是一个普通的 js object, 其改变不会触发 re-render
let timeoutId = null
是不会存活在组件的下一次渲染中的, 可以将 timeoutId
保存在 ref
之中
ref 操作 DOM
获取指向节点的Ref
低级组件(input)需要暴露DOM, 或者需要使用React中不存在的API, focus scrollIntoView
访问另一个自定义组件的DOM
forwardRef + useImperativeHandle
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
const MyInput = forwardRef((props, ref) => {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
// 只暴露 focus,没有别的
focus() {
realInputRef.current.focus();
},
}));
return <input {...props} ref={realInputRef} />;
});
React 什么时候添加Ref
需要在可以操作dom的时候再增加ref . flashSync
function handleAdd() {
const newTodo = { id: nextId++, text: text };
flushSync(() => {
setText('');
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
使用Effect同步
Effect 会在渲染之后执行一些逻辑,以便与React
系统之外(浏览器 API、第三方小部件,以及网络)进行同步. 比如 与服务器进行连接, **发送日志 **,控制 **非React组件 **(useRef)
Effect和Event的区别
Effect 不是像 Event一样通过特定的用户事件引起的, 而是渲染本身引起的副作用
编写 Effect steps
- 声明Effect
- 添加依赖
- 按需添加cleanup函数,防止内存泄漏
开发环境执行Effect两次
- 订阅+退订
- 获取数据+中止
- 不要执行商品购买操作
- 应用程序启动时执行
移除不必要的 Effect
useSyncExternalStore
比如, 如果想根据state计算另一个state就不应使用Effect
几种不要的Effect的场景
- 根据props/state 更新state -> 渲染时更新
- props 变化的时候重置state -> key
- props 变化的时候更新-> state 结构
- 事件函数共享逻辑 -> 公共函数
- 链式计算 -> 事件处理函数中计算
- 初始化应用 -> 添加顶层变量/移出来
- 通知父组件state变化 -> 对应的事件处理函数中
- 数据传递给父组件 -> 父组件传递数据给子组件
- 订阅外部store -> useSyncExternalStore
- 获取数据(这不是键入事件) -> 自定义hook
useData
清理函数
Effect LifeCycle
React Effect
的生命周期存在两个阶段:开始同步信息(Effect 主体部分),以及停止同步信息(Effect 的清理函数)
选择 Effect 的依赖项
依赖项可能导致出现无限循环的问题,或者 Effect 过于频繁地重新进行同步
- 检查 Effect 是否表示了独立的同步
- Event 和 Effect 分离
- 避免将渲染期间计算出来的对象和函数作为依赖项
事件从Effect中分开
当你想让Effect
中的一段代码不是响应的,应该如何做
在React Effect
中,代码逻辑依赖一些 响应值,然而不希望它随着响应值改变而同步运行。此时,我们可以将这段逻辑放在 useEffectEvent 这个试验性 API 中
useEffectEvent
- 只在 Effect 内部调用,隶属于 Effect 逻辑一部分
- 不要传递给其他组件
import { experimental_useEffectEvent as useEffectEvent } from 'react';
移除不必要的Effect Dependency
不必须的dependency导致 re-xxx
Effect的依赖应该和Effect中的代码相对应,当移除Effect的X依赖的时候,要证明X不是依赖(改变依赖需要改变代码);
依赖object
解决方案是将对象中的state
解构出来
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection();
connection.connect();
connection.on('message', (receivedMessage) => {
// 🔴 改成函数形式,并移除 `message` 依赖
setMessages([...messages, receivedMessage]);
setMessages(msgs => [...msgs, receivedMessage]);
});
return () => connection.disconnect();
}, [roomId, messages]); // ✅ All dependencies declared
// ...
组件间逻辑复用useHooks
自定义Hook, usxXXX 共享状态逻辑 而不是状态本身,最好抽离的是Effect
相关
Keep in mind
- refs 和 Effect 是 react 的一种脱围机制
- do not suppress the dependency linter
- 让你的自定义hook专注于具体的高级用例, 以
用途名称
命名自定义 hook