一、React-Hooks 的使用原则

  • 只在 React 函数中调用 Hook;
  • 不要在循环、条件或嵌套函数中调用 Hook;
  • 确保 Hooks 在每次渲染时都保持同样的执行顺序。

二、分析 React-Hooks 的调用链路

1. 首次渲染过程:

image.png
在这个流程中,useState 触发的一系列操作最后会落到 mountState 里面去,所以提取一下mountState的源码。

  1. // 进入 mounState 逻辑
  2. function mountState(initialState) {
  3. // 将新的 hook 对象追加进链表尾部
  4. var hook = mountWorkInProgressHook();
  5. // initialState 可以是一个回调,若是回调,则取回调执行后的值
  6. if (typeof initialState === 'function') {
  7. // $FlowFixMe: Flow doesn't like mixed types
  8. initialState = initialState();
  9. }
  10. // 创建当前 hook 对象的更新队列,这一步主要是为了能够依序保留 dispatch
  11. const queue = hook.queue = {
  12. last: null,
  13. dispatch: null,
  14. lastRenderedReducer: basicStateReducer,
  15. lastRenderedState: (initialState: any),
  16. };
  17. // 将 initialState 作为一个“记忆值”存下来
  18. hook.memoizedState = hook.baseState = initialState;
  19. // dispatch 是由上下文中一个叫 dispatchAction 的方法创建的,这里不必纠结这个方法具体做了什么
  20. var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
  21. // 返回目标数组,dispatch 其实就是示例中常常见到的 setXXX 这个函数,想不到吧?哈哈
  22. return [hook.memoizedState, dispatch];
  23. }

从这段源码中我们可以看出,mounState 的主要工作是初始化 Hooks。在整段源码中,最需要关注的是 mountWorkInProgressHook 方法,它为我们道出了 Hooks 背后的数据结构组织形式。以下是 mountWorkInProgressHook 方法的源码:

  1. function mountWorkInProgressHook() {
  2. // 注意,单个 hook 是以对象的形式存在的
  3. var hook = {
  4. memoizedState: null,
  5. baseState: null,
  6. baseQueue: null,
  7. queue: null,
  8. next: null
  9. };
  10. if (workInProgressHook === null) {
  11. // 这行代码每个 React 版本不太一样,但做的都是同一件事:将 hook 作为链表的头节点处理
  12. firstWorkInProgressHook = workInProgressHook = hook;
  13. } else {
  14. // 若链表不为空,则将 hook 追加到链表尾部
  15. workInProgressHook = workInProgressHook.next = hook;
  16. }
  17. // 返回当前的 hook
  18. return workInProgressHook;
  19. }

到这里可以看出,hook 相关的所有信息收敛在一个 hook 对象里,而 hook 对象之间以单向链表的形式相互串联。

2. 更新过程:

image.png

根据图中高亮部分的提示不难看出,首次渲染和更新渲染的区别,在于调用的是 mountState,还是 updateState。mountState 做了什么,你已经非常清楚了;而 updateState 之后的操作链路,虽然涉及的代码有很多,但其实做的事情很容易理解:按顺序去遍历之前构建好的链表,取出对应的数据信息进行渲染。

我们把 mountState 和 updateState 做的事情放在一起来看:mountState(首次渲染)构建链表并渲染;updateState 依次遍历链表并渲染。

hooks 的渲染是通过“依次遍历”来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果自然是不可控的。

结论:Hooks 的本质其实是链表。**