想写这篇文的起因是帷幄的二面过程中的一道面试题,有点深度。虽然一直知道 hooks 不能写在一些条件语句中,但是没有深入了解过原因。之前面试也没有被问到这个问题,所以整理一篇文档记录学习一下。
- React Hooks 的使用限制
官网是这样解释的:
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
- 限制的原因
这个限制并不是 React 团队凭空造出来的,的确是由于 React Hook 的实现设计而不得已为之。
因为在 hooks 规则中说到
- 只在顶层调用 hooks
- 不在循环、条件语句和嵌套函数内调用 hooks
- 在 return 之前调用
- 保证每次渲染时 hooks 的调用顺序是一致的
- 只在 function component 组件中使用 hooks
那么为什么只能在顶层调用 hook?
因为可以在一个函数组件中多次调用 state 和 hooks,那么如何知道 state 是与哪个 useState
相对应?
答案是:react 依赖 hooks 调用的顺序
如果我们在循环、条件语句或嵌套函数内调用 hooks,那么在每次渲染的时候,有可能会因为条件的不同,导致 hooks 的调用顺序不同,而无法获取到正确的 state 或运行出错,所以,要在顶层调用 hook。
那为什么渲染会出错呢?就必须提到 hooks 挂载数据的数据结构叫做 [text class="text-danger"] fiber [/text],hooks 就是通过把数据挂载到组件对应的 fiber 节点上来实现的。
- 什么是 fiber
fiber 是 React16 引入的架构变动,界面通过 vdom 描述,但是不是直接手写 vdom,而是 jsx 编译产生的 render function 之后以后生成的。这样就可以加上 state、props 和一些动态逻辑,动态产生 vdom。
vdom 生成之后不再是直接渲染,而是先转成 fiber,这个 vdom 转 fiber 的过程叫做 reconcile。
同时,fiber 是一个链表结构,可以打断,这样就可以通过 requestIdleCallback 来空闲调度 reconcile,这样不断的循环,直到处理完所有的 vdom 转 fiber 的 reconcile,就开始 commit,也就是更新到 dom。
reconcile 的过程会提前创建好 dom,还会标记出增删改,那么 commit 阶段就很快了。
从之前递归渲染时做 diff 来确定增删改以及创建 dom,提前到了可打断的 reconcile 阶段,让 commit 变得非常快,这就是 fiber 架构的目的和意义。所有 hooks api 都是基于 fiber 节点上的 memorizedState 链表来存取数据并完成各自的逻辑的。