开发过程中,如果在条件语句中调用 hooks,React 会抛出错误。因为 hooks 的底层设计数据结构是链表,React 使用链表来保证 hooks 的顺序。

Hooks 调用链

一个典型的 useState 使用场景

const [name, setName] = useState('');

//...

setName('AB');

Hooks 的调用链路在“首次渲染”和“更新阶段”是不同的。

首次渲染

Hooks Init

在这个流程中,useState 触发的一系列操作最后会落到 mountState 里面去,所以需要重点关注的就是 mountState 做了什么事情。

function mountStateImpl(initialState) {
  // 将新的 hook 对象追加到链表尾部
  const hook = mountWorkInProgressHook();

  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;

  // 创建当前 hook 对象的更新队列,主要是为了能够一次保留 dispatch
  const queue = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  };
  hook.queue = queue;
  return hook;
}

function mountState(initialState) {
  const hook = mountStateImpl(initialState);
  const queue = hook.queue;
  const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
  queue.dispatch = dispatch;
  return [hook.memoizedState, dispatch];
}

mounState 的主要工作是初始化 Hooks。最需要关注的是 mountWorkInProgressHook 方法,它为我们道出了 Hooks 背后的数据结构组织形式。

function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // 链表中首个 hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 将 hook 插入到链表尾部
    workInProgressHook = workInProgressHook.next = hook;
  }

  return workInProgressHook;
}

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

更新阶段

Hooks Update

按顺序去遍历之前构建好的链表,取出对应的数据信息进行渲染。mountState(首次渲染)构建链表并渲染;updateState 依次遍历链表并渲染。

必须按照顺序调用从根本上来说是因为 useState 这个钩子在设计层面并没有“状态命名”这个动作,也就是说你每生成一个新的状态,React 并不知道这个状态名字叫啥,所以需要通过顺序来索引到对应的状态值

参考链接