Hooks 原理概览
什么是 Hook?
Hook 是一个特殊的函数, 它可以让你“钩入” React 的特性. 如, useState 是允许你在 React 函数组件中添加 state 的 Hook.
在 v17.0.2 中, 共定义了 14 种 Hook
export type HookType =
| "useState"
| "useReducer"
| "useContext"
| "useRef"
| "useEffect"
| "useLayoutEffect"
| "useCallback"
| "useMemo"
| "useImperativeHandle"
| "useDebugValue"
| "useDeferredValue"
| "useTransition"
| "useMutableSource"
| "useOpaqueIdentifier";
Hook 数据结构
type Update<S, A> = {
lane: Lane,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update<S, A>,
priority?: ReactPriorityLevel,
};
type UpdateQueue<S, A> = {
pending: Update<S, A> | null,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
};
export type Hook = {
memoizedState: any, // 当前状态
baseState: any, // 基状态
baseQueue: Update<any, any> | null, // 基队列
queue: UpdateQueue<any, any> | null, // 更新队列
next: Hook | null, // next指针
};
- hook.memoizedState: 保持在内存中的局部状态.
- hook.baseState: hook.baseQueue 中所有 update 对象合并之后的状态.
- hook.baseQueue: 存储 update 对象的环形链表, 只包括高于本次渲染优先级的 update 对象.
- hook.queue: 存储 update 对象的环形链表, 包括所有优先级的 update 对象.
- hook.next: next 指针, 指向链表中的下一个 hook.
Hook 与 Fiber
使用 Hook 最终也是为了控制 fiber 节点的状态和副作用
export type Fiber = {
// 1. fiber节点自身状态相关
pendingProps: any;
memoizedProps: any;
updateQueue: mixed;
memoizedState: any;
// 2. fiber节点副作用(Effect)相关
flags: Flags;
nextEffect: Fiber | null;
firstEffect: Fiber | null;
lastEffect: Fiber | null;
};
使用 Hook 的任意一个 api, 最后都是为了控制上述这几个 fiber 属性.
我们之前有大概了解了 Fiber,那么看下 Fiber 和 Hook 有什么关系吧,在这之前我们还是以一个组件做示例
function App() {
// 1. useState
const [count, setCount] = useState(0);
// 2. useEffect
useEffect(() => {
console.log(`effect 1 created`);
});
// 3. useState
const [name, setName] = useState("John");
// 4. useEffect
useEffect(() => {
console.log(`effect 2 created`);
});
return (
<>
<h1
onClick={() => {
setCount(() => count + 1);
}}
>
<p title={count}>{count}</p> {name}
</h1>
</>
);
}
在这个 function 组件中, 同时使用了状态 Hook 和副作用 Hook.
初次渲染时, 逻辑执行到
performUnitOfWork
->beginWork
->updateFunctionComponent
->renderWithHooks
前,没有右侧侧黄色部分
只有调用了renderWithHooks后才开始有右侧黄色部分
无论状态 Hook 或副作用 Hook 都按照调用顺序存储在 fiber.memoizedState 链表中
fiber树更新阶段, 把current.memoizedState链表上的所有Hook按照顺序克隆到workInProgress.memoizedState上, 实现数据的持久化.
注意
其中 hook.queue 与 fiber.updateQueue 虽然都是 update 环形链表, 尽管 update 对象的数据结构与处理方式都高度相似, 但是这 2 个队列中的 update 对象是完全独立的. hook.queue 只作用于 hook 对象的状态维护, 切勿与 fiber.updateQueue 混淆.
为什么hooks不能写在条件判断中?
hook会按顺序存储在链表中,如果写在条件判断中,就没法保持链表的顺序