初始化阶段,在一个函数组件第一次渲染执行上下文过程中,每个react-hooks执行,都会产生一个hook对象,并形成链表结构,绑定在workInProgress的memoizedState属性上,然后react-hooks上的状态,绑定在当前hooks对象的memoizedState属性上。对effect副作用钩子,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树构建完成,在执行每个 effect 副作用钩子。
1.mountWorkInProgressHook
当我们看 HooksDispatcherOnMount 中 每个hook 调用的函数里,都会先调用mountWorkInProgressHook 函数,下面我们来看下这个函数主要做了什么。
react-reconciler/src/ReactFiberHooks.js
function mountWorkInProgressHook(): Hook {
// 创建hooks对象
const hook: Hook = {
memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
baseState: null, // useState和 useReducer中,一次更新中,产生的最新 state值。
baseQueue: null, // useState和useReducer中 保存最新的更新队列。
queue: null, // 保存待更新队列 pendingQueue ,更新函数 dispatch 等信息。
next: null, // 指向下一个 hooks对象。
};
// 函数组件中第一个hooks
if (workInProgressHook === null) {
// hooks链表中的第一个
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 添加到 链表的最后
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
每次执行一个hooks函数,都产生一个hook对象,里面保存了当前hooks信息,然后将每个hook 对象以链表形式串联起来,并赋值给workInProgress的memoizedState。所以, hooks 通过链表顺序来证明唯一性。
2. mountState(初始化useState) and mountReducer(初始化useReducer)
当我们函数组件初始化,执行useState 或useReducer hooks的时候,会执行mountState 或 mountReducer函数。
// useState
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// 如果 useState 第一个参数为函数,执行函数得到state
initialState = initialState();
}
// 将初始化的state赋值给hook对象的memoizedState 和baseState
hook.memoizedState = hook.baseState = initialState;
// 创建一个queue对象,里面保存了负责更新的信息
const queue = (hook.queue = {
pending: null, // 待更新的
interleaved: null,
lanes: NoLanes,
dispatch: null, // 更新函数
lastRenderedReducer: basicStateReducer, // 用于得到最新的state
lastRenderedState: (initialState: any), // 最后一次得到的 state
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
// useReducer function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
}
);
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any
));
return [hook.memoizedState, dispatch];
}
useState和useReducer触发函数更新的方法都是dispatchAction,useState可以看成一个简化版的useReducer。
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
// 1.创建update对象,记录此次更新的信息,并放入待更新的pending队列
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any), // 下一个更新对象
};
const alternate = fiber.alternate;
// 2.判断函数组件当前是否在渲染阶段
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 3.渲染阶段的更新,将他缓存在待更新队列里。等渲染通过后,我们将在workInProgress hook的最顶端重新开始并调用缓存的更新。
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
// 将待更新队列取出
const pending = queue.pending;
if (pending === null) {
// 第一次更新. 创建一个循环列表.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update; // 将update对象放入待更新队列
} else {
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = queue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
update.next = update;
// At the end of the current render, this queue's interleaved updates will
// be transfered to the pending queue.
pushInterleavedQueue(queue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
queue.interleaved = update;
} else {
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 3.当前组件没有处于调和渲染阶段,这个时候queue对象为空,我们可以在进入渲染阶段之前即刻计算下次的state值。
// 如果这个新值和现在的state值相等(浅比较),我们也许能够完全退出。(证实useState, 2次值相等的时候,组件不渲染)
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any); // 上一次的state
const eagerState = lastRenderedReducer(currentState, action); // 获取的新的state
// 用reducer计算,并缓存这个即刻计算的state值到更新对象上。如果在我们进入渲染阶段的时候,
// reducer并没有变化,这个即刻计算的state值将不会再被用来调用reducer。
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 如果2次的的state相同,我们可以完全退出,避免react 重新渲染。
// 如果组件因为其他原因导致重新渲染,在reducer改变时我们也会重置这个update对象。
return;
}
}
}
}
// 如果2次state不相等,则调度scheduleUpdateOnFiber(react渲染更新的主要函数) 渲染当前fiber
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (isTransitionLane(lane) && root !== null) {
let queueLanes = queue.lanes;
// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
queue.lanes = newQueueLanes;
// Even if queue.lanes already include lane, we don't know for certain if
// the lane finished since the last time we entangled it. So we need to
// entangle it again, just to be sure.
markRootEntangled(root, newQueueLanes);
}
}
if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
}
3.mountEffect(初始化useEffect) and mountLayoutEffect(初始化useLayoutEffect)
当我们函数组件初始化,执行useEffect 或useLayoutEffect hooks的时候,会执行mountEffect 或mountLayoutEffect函数。
// useEffect
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
// useLayoutEffect
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
let fiberFlags: Flags = UpdateEffect;
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStaticEffect;
}
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}
function pushEffect(tag, create, destroy, deps) {
// 创建effect 对象
const effect: Effect = {
tag,
create,
destroy,
deps,
// Circular
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
// 判断组件是否是第一渲染
if (componentUpdateQueue === null) {
// 第一次渲染创建componentUpdateQueue, 然后放入workInProgress 的updateQueue中。
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 将effect 对象插入lastEffect 循环链表中
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
拓展:effectList
effect list 可以理解为是一个存储 effectTag 副作用列表容器。它是由 fiber 节点和指针 nextEffect 构成的单链表结构,这其中还包括第一个节点 firstEffect ,和最后一个节点 lastEffect。
React 采用深度优先搜索算法,在 render 阶段遍历 fiber 树时,把每一个有副作用的 fiber 筛选出来,最后构建生成一个只带副作用的 effect list 链表。
在 commit 阶段,React 拿到 effect list 数据后,通过遍历 effect list,并根据每一个 effect 节点的 effectTag 类型,执行每个effect,从而对相应的 DOM 树执行更改。
4. mountImperativeHandle(初始化useImperativeHandle)
当我们函数组件初始化,执行useImperativeHandle时候,会执行mountImperativeHandle函数。
function mountImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
// TODO: If deps are provided, should we skip comparing the ref itself?
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
let fiberFlags: Flags = UpdateEffect;
if (enableSuspenseLayoutEffectSemantics) {
fiberFlags |= LayoutStaticEffect;
}
return mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
function imperativeHandleEffect<T>(
create: () => T,
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
) {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
// 通过create() 返回的值定制ref.current 值
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
5.mountMemo(初始化useMemo) and mountCallback(初始化useCallback)
当我们函数组件初始化,执行useMemo 或useCallback hooks的时候,会执行mountMemo 或mountCallback函数。从下面代码可以看出2个hooks 的区别就在于,hook对象的memoizedState 缓存的值不一样。useMemo 存的是计算后的值和依赖,而useCallback 存的是计算本身和依赖。
// useMemo
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
// useCallback
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
6.mountRef(初始化useRef)
当我们函数组件初始化,执行useRef hooks的时候,会执行mountRef函数,他会在hooks 对象的memoizedState 里存入一个包含current 的对象。
function mountRef<T>(initialValue: T): {|current: T|} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
下一篇,我们将学习下hooks的更新。
最后推荐下我的个人网站- 【良月清秋的前端日志】(animasling.github.io/front-end-b…) ,希望我的文章对你有帮助。