React hooks 的使用原则
- 只在React 函数中调用Hook
- 不要再循环、条件或嵌套函数中调用Hook
1、为什么“不要再循环、条件或嵌套函数中调用Hook”
为了确保Hooks在每次渲染时都保持同样的执行顺序
2、 Hooks 的正常运作,在底层依赖于顺序链表
以 useState 为例,分析 React-Hooks 的调用链路
hooks 首次渲染
mountState源码
mounState 的主要工作是初始化 Hooks
// 进入 mounState 逻辑
function mountState(initialState) {
// 将新的 hook 对象追加进链表尾部
var hook = mountWorkInProgressHook();
// initialState 可以是一个回调,若是回调,则取回调执行后的值
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
// 创建当前 hook 对象的更新队列,这一步主要是为了能够依序保留 dispatch
const queue = hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
// 将 initialState 作为一个“记忆值”存下来
hook.memoizedState = hook.baseState = initialState;
// dispatch 是由上下文中一个叫 dispatchAction 的方法创建的,这里不必纠结这个方法具体做了什么
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
// 返回目标数组,dispatch 其实就是示例中常常见到的 setXXX 这个函数
return [hook.memoizedState, dispatch];
}
mountWorkInProgressHook 方法的源码
function mountWorkInProgressHook() {
// 注意,单个 hook 是以对象的形式存在的
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
// 这行代码每个 React 版本不太一样,但做的都是同一件事:将 hook 作为链表的头节点处理
firstWorkInProgressHook = workInProgressHook = hook;
} else {
// 若链表不为空,则将 hook 追加到链表尾部
workInProgressHook = workInProgressHook.next = hook;
}
// 返回当前的 hook
return workInProgressHook;
}
结论: hook 相关的所有信息收敛在一个 hook 对象里,而 hook 对象之间以单向链表的形式相互串联。
hooks 更新过程
更新过程做的事情:按顺序去遍历之前构建好的链表,取出对应的数据信息进行渲染
总结
1、 mountState(首次渲染)构建链表并渲染;updateState 依次遍历链表并渲染
2、 hooks 的渲染是通过“依次遍历”来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果是不可控的
3、Hooks 的本质是链表