不能在循环、条件或嵌套中调用Hooks
2023 react开发过程中,如果在条件语句中调用 hooks,React 会抛出错误。因为 hooks 的底层设计数据结构是链表,React 使用链表来保证 hooks 的顺序。
Hooks 调用链
一个典型的 useState 使用场景
const [name, setName] = useState('');
//...
setName('AB');
Hooks 的调用链路在“首次渲染”和“更新阶段”是不同的。
首次渲染
在这个流程中,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 对象之间以“单向链表”的形式相互串联。
更新阶段
按顺序去遍历之前构建好的链表,取出对应的数据信息进行渲染。mountState(首次渲染)构建链表并渲染;updateState 依次遍历链表并渲染。
必须按照顺序调用从根本上来说是因为 useState 这个钩子在设计层面并没有“状态命名”这个动作,也就是说你每生成一个新的状态,React 并不知道这个状态名字叫啥,所以需要通过顺序来索引到对应的状态值