跳到主要内容

认识

一、认识


为了能让函数组件可以保存一些状态,执行一些副作用钩子,React Hooks 应运而生,它可以帮助记录 React 中组件的状态,处理一些额外的副作用。

二、规则


Hooks 使用规则:

  • 只能在函数内部的最外层调用Hook,不要在循环、条件判断或者子函数中调用

  • 只能在React的函数组件中调用Hook,不要在其他JavaScript函数中调用

  • 命令规范

    • useState返回数组的第二项以set开头(仅作为约定)

    • 自定义Hooksuse开头(可被 lint 校验)

三、问题


3.1 为什么不可以在函数组件外部使用 Hooks ?

Mount 阶段, Render BeginWork 递阶段, 从根 Fiber 开始, 对每个 Fiber 节点,调用对应的处理函数。处理逻辑如下(主要以函数组件为例): 遇到函数组件, 调用 renderWithHooks 执行函数组件。在执行过程中, React 按照 Hook 调用的顺序遍历组件内部所有 Hook, 在 Mount 阶段的每一个 Hook 会调用 MountHookXX 来为每一个 Hook 创建一个 Hook 对象, 存储在 当前处理的 fiber.memoizedState 中(注意, 如果 Hook 不在函数组件中调用, 此时获取不到当前正在处理的 Fiber, 无法进行后续流程) 。

Update 阶段, Render BeginWork 递阶段, 从根 Fiber 开始, 对每个 Fiber 节点,调用对应的处理函数。处理逻辑如下(主要以函数组件为例): 遇到函数组件, 调用 renderWithHooks 执行函数组件。在执行过程中, 从 当前处理的 Fiber.memoizedState 中获取 Hook 链表。(注意, 如果 Hook 不在函数组件中调用, 此时获取不到当前正在处理的 Fiber, 无法进行后续流程) 。

3.2 为什么不可以在条件语句、循环条件里面使用 Hooks ?

Mount 阶段, Render BeginWork 递阶段, 从根 Fiber 开始, 对每个 Fiber 节点,调用对应的处理函数。处理逻辑如下(主要以函数组件为例): 遇到函数组件, 调用 renderWithHooks 执行函数组件。在执行过程中, React 按照 Hook 调用的顺序遍历组件内部所有 Hook, 在 Mount 阶段的每一个 Hook 会调用 MountHookXX 来为每一个 Hook 创建一个 Hook 对象, 存储在 当前处理的 fiber.memoizedState 中。此时, Hooks 链表中的 Hook 顺序与函数组件中的 Hook 调用顺序一致。

Update 阶段, Render BeginWork 递阶段, 从根 Fiber 开始, 对每个 Fiber 节点,调用对应的处理函数。处理逻辑如下(主要以函数组件为例): 遇到函数组件, 调用 renderWithHooks 执行函数组件。在执行过程中, 从 当前处理的 Fiber.memoizedState 中获取 Hook 链表, 依次遍历 Hooks 链表。

因此, 必须保证 mount 阶段update 阶段 阶段的 hook 执行顺序一致。否则, 有可能在其中一次的渲染中, 因为一个 hook 没有执行, currentHook.next 与 将要执行的 hook 明显是不对应的,造成代码错误。

结论: 凡是使用 currentHookworkInProgressHook 来获取 hookhook, 都必须放在顶部, 绝对不可以在条件和循环里使用, 必须保证 hooks 在每一次渲染中都按照同样的顺序被调用。

3.3 Vue 组合函数与 React Hooks 的区别?

React HooksRender BeginWork 递阶段, 遇到函数组件 Fiber, 执行函数组件, 在执行过程中, React 按照 Hook 调用的顺序遍历组件内部所有 Hook。也就是说, React Hooks 在组件每次更新时都会重新调用。并且, 由于 React Hooks 的实现方式, Mount 阶段和 Update 中的 Hooks 是不同的逻辑。因此, Hooks 有严格的调用顺序,并不可以写在条件分支中。因为, 有可能在其中一次的渲染中, 因为一个 hook 没有执行, currentHook.next 与 将要执行的 hook 明显是不对应的, 造成代码错误。 其中, 基于依赖项的 Hooks 具有闭包问题, 需要传入正确的依赖数组, 或者需要通过 useRef 来保持状态。

Vue 组合函数 仅会在 setup() 或者 <script setup> 中调用一次, 组合式 API 也并不限制调用顺序,还可以有条件地进行调用。针对依赖项的 Hooks, Vue 的响应性系统运行时会自动收集计算属性和侦听器的依赖,因此无需手动声明依赖, 不会存在闭包问题。

因此, Vue 组合函数 具有后发优势, 基于 React Hooks 的设计理念, 结合 Vue 自身响应式机制, 实现了一种更好用的 Hooks

3.4 Vue 3.0 Hooks 与 React Hooks 区别?

React HooksRender BeginWork 递阶段, 遇到函数组件 Fiber, 执行函数组件, 在执行过程中, React 按照 Hook 调用的顺序遍历组件内部所有 Hook。也就是说, React Hooks 在组件每次更新时都会重新调用。并且, 由于 React Hooks 的实现方式, Mount 阶段和 Update 中的 Hooks 是不同的逻辑。因此, Hooks 有严格的调用顺序,并不可以写在条件分支中。因为, 有可能在其中一次的渲染中, 因为一个 hook 没有执行, currentHook.next 与 将要执行的 hook 明显是不对应的, 造成代码错误。 其中, 基于依赖项的 Hooks 具有闭包问题, 需要传入正确的依赖数组, 或者需要通过 useRef 来保持状态。

Vue 组合函数 仅会在 setup() 或者 <script setup> 中调用一次, 组合式 API 也并不限制调用顺序,还可以有条件地进行调用。针对依赖项的 Hooks, Vue 的响应性系统运行时会自动收集计算属性和侦听器的依赖,因此无需手动声明依赖, 不会存在闭包问题。

因此, Vue 组合函数 具有后发优势, 基于 React Hooks 的设计理念, 结合 Vue 自身响应式机制, 实现了一种更好用的 Hooks