本文章分析基于react 18.2源码分析
hook 原理
所有的Hooks在React.js中被引入,挂载在React对象中
// React.js
import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
} from './ReactHooks';
ReactFiberHooks.old
/*
简单分析一下 Hook对象
在函数组件之中,react并不知道我们调用了几次hook,所以react将对象挂载在fiber.memorizedStated上来保存函数组件的state
memoizedState: 是用来记录当前useState、useReducer应该返回的结果的
queue: 缓存队列,用来记录多次更新行为
next:指向下一个hook的指针
*/
fiber .memorizedState(hook0)-> next(hook1)-> next(hook2)->next(hook3) (workInProgressHook)
export type Hook = {|
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: any,
next: Hook | null,
|};
memoizedState的结构如下
useState
初始化时
创建一个新的hook,初始化state, 并绑定触发器
初始化阶段ReactCurrentDispatcher.current 会指向HooksDispatcherOnMount 对象
const HooksDispatcherOnMount: Dispatcher = {
readContext,
/** 省略其它Hooks **/
useState: mountState, // 函数组件useState挂载时调用
};
/*
useState 挂载时调用 mountState
*/
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
// initialState () => S 初始化为函数则优先调用函数,但是不提供参数
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
/*
进行state的初始化工作
S 值则直接通过 memoizedState赋值
hook.baseState 记录当前值 ----> 用于更新相同值时做视图渲染判断
*/
hook.memoizedState = hook.baseState = initialState;
/*
进行queue的初始化工作
因为 hook作为单链表存在于fiber,所以在function组件中不能将hook置于条件判断中
*/
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
/*
currentlyRenderingFiber 用于获取当前 fiber
dispatch 返回触发器
*/
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
//绑定当前fiber结点和queue
currentlyRenderingFiber,
queue,
): any));
// 返回初始state和触发器
return [hook.memoizedState, dispatch];
}
更新时
更新state值时,获取该Hook对象中的 queue,内部存有本次更新的一系列数据,进行更新
更新阶段 ReactCurrentDispatcher.current 会指向HooksDispatcherOnUpdate对象
const HooksDispatcherOnUpdate: Dispatcher = {
/** 省略其它Hooks **/
useState: updateState,
}
// 可以看到updateReducer的过程与传的initalState已经无关了,所以初始值只在第一次被使用
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
源码路径:ReactFiberHooks.old/updateReducer
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 获取初始化时的 hook
const hook = updateWorkInProgressHook();
// 获取Hook对象上的 queue
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
if (baseQueue !== null) {
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
const first = baseQueue.next;
// 获取当前 State
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
lane: NoLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Process this update.
if (update.hasEagerState) {
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const lastInterleaved = queue.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
const interleavedLane = interleaved.lane;
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
interleavedLane,
);
markSkippedUpdateLanes(interleavedLane);
interleaved = ((interleaved: any).next: Update<S, A>);
} while (interleaved !== lastInterleaved);
} else if (baseQueue === null) {
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
// 返回新的 state,及更新 hook 的 dispatch 方法
return [hook.memoizedState, dispatch];
}
总结
单个hooks的更新行为全都挂在Hooks.queue下,所以能够管理好queue的核心就在于
- 初始化queue - mountState
- 维护queue - dispatchAction
- 更新queue - updateReducer
Q: 为什么不能在循环/条件语句中执行
以useState为例:
和类组件存储state不同,React并不知道我们调用了几次useState,对hooks的存储是按顺序的(参见Hook结构),一个hook对象的next指向下一个hooks。所以当我们建立示例代码中的对应关系后,Hook的结构如下:
// hook1: const [count, setCount] = useState(0) — 拿到state1
{
memorizedState: 0
next : {
// hook2: const [name, setName] = useState('Zzz') - 拿到state2
memorizedState: 'Zzz'
next : {
null
}
}
}
// hook1 => Fiber.memoizedState
// state1 === hook1.memoizedState
// hook1.next => hook2
// state2 === hook2.memoizedState
所以如果把hook1放到一个if语句中,当这个没有执行时,hook2拿到的state其实是上一次hook1执行后的state(而不是上一次hook2执行后的)。这样显然会发生错误。
useState总结
初始化: 构建dispatcher函数和初始值
更新时:
- 调用dispatcher函数,按序插入update(其实就是一个action)
- 收集update,调度一次React的更新
- 在更新的过程中将
ReactCurrentDispatcher.current指向负责更新的Dispatcher - 执行到函数组件时,
useState会被重新执行,在resolve dispatcher的阶段拿到了负责更新的dispatcher。 useState会拿到Hook对象,Hook.queue中存储了更新队列,依次进行更新后,即可拿到最新的state- 函数组件App()执行后返回的nextChild中的count值已经是最新的了。FiberNode中的
memorizedState也被设置为最新的state - Fiber渲染出真实DOM。更新结束。
感谢阅读,如有错误尽请各位批评指正~