想写这篇文的起因是帷幄的二面过程中的一道面试题,有点深度。虽然一直知道 hooks 不能写在一些条件语句中,但是没有深入了解过原因。之前面试也没有被问到这个问题,所以整理一篇文档记录学习一下。

  1. React Hooks 的使用限制

官网是这样解释的:

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。

  1. 限制的原因

这个限制并不是 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 节点上来实现的。

  1. 什么是 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 链表来存取数据并完成各自的逻辑的。