不要类比 class 组件里的生命周期
useEffect(fn, [])
只会执行一次,但是不完全等于componentDidMount
生命周期。如果将这两种一一对比,对 Effect 的使用就很难提升了,踩坑之后的领悟。
useEffect 使用
- 把不依赖 props 和 state 的函数提到你的组件外面
- 把那些仅被 effect 使用的函数放到 effect 里面
- 如果这样做了以后,你的 effect 还是需要用到组件内的函数,包括 props 传进来的函数,则使勇敢 useCallback 包一层
出现无限重复请求的情况
- useEffect 没有设置第二个参数依赖数组,即
useEffect(fn)
,则每一次浏览器渲染后都会执行 useEffect,然后在 effect 中更新了状态引起渲染并再次触发 effect; - 设置的依赖数组总是在变化,
useEffect(fn, [a, b, c])
,比如函数引用会导致无限循环,解决办法是 1)把函数放到 effect 里,2)把函数提到组件外面,3)用 useCallBack 包裹,4)也可以使用 useMemo 处理
Effect拿到的总是定义它的那次渲染中的props和state
,如果你觉得在渲染中拿到了一些旧的 props 和 state,且不是你想要的,那么很大可能就是你遗漏里一些 useEffect 的依赖项,导致了 bug,可以通过 eslint-plugin-react-hooks
的exhaustive-deps
规则来提出警告并修改,避免因漏了依赖产的 bug。
useEffect 执行时机
浅浅的看下执行过程:
/packages/react/src/ReactHooks.js
function useEffect
- 获取 dispatcher 对象
dispatcher = resolveDispatcher()
ReactCurrentDispatcher.current
,这个值要么null | Dispatcher
,
Dispatcher 是这么一个对象
1 | export type Dispatcher = {| |
执行 useEffect(/packages/react-reconciler/src/ReactFiberHooks.new.js)
给当前的currentHookNameInDev = 'useEffect'
;赋值let currentHookNameInDev: ?HookType = null;
通过 mountHookTypesDev 方法,将当前 hook push 到 hookTypesDev 这个数组里,
hookTypesDev 定义:
let hookTypesDev: Array<HookType> | null = null;
,这个列表存储初始化 render 时 hook 的调用顺序检查
checkDepsAreArrayDev
依赖是否合法执行
mountWorkInProgressHook
,返回workInProgressHook
设置
currentlyRenderingFiber$1.effectTag |= fiberEffectTag;
currentlyRenderingFiber 表示 work-in-progress fiber
设置 hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps)
pushEffect 做了什么:
1 | const effect: Effect = { |
- mountIndeterminateComponent 挂载好 jsx 后,将 isRendering=false,然后经过一系列调度、协调(这个过程太长了,没理解透。。。),在 commitHookEffectListMount 执行了 effect 里传进的回调
自定义 hook 使用
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
1 | const useDataApi = (initialUrl, initialData) => { |
函数式组件和类组件的区别
函数式组件和类组件的最大区别在于:函数式组件捕获了渲染所用的值
1 | function MessageThread() { |
如果我发送一条特定的消息,组件不应该对实际发送的是哪条消息感到困惑。这个函数组件的 message 变量捕获了“属于”返回了被浏览器调用的单击处理函数的那一次渲染。所以当我点击“发送”时 message 被设置为那一刻在 input 中输入的内容。
但是如果我们想要读取并不属于这一次特定渲染的,最新的 props 和 state 呢?在函数式组件中,你也可以拥有一个在所有的组件渲染帧中共享的可变变量。它被成为“ref”:
1 | function MessageThread() { |
在处理类似于 intervals 和订阅这样的命令式 API 时,ref 会十分便利