了解react的整体流程,会有助于理解本文。
要了解hooks是什么,我们得先想知道react怎么执行函数组件。
先看看函数组件的fiber是什么?
const fiber = {
type: f App(){}, //函数本身,
memoziedState: {}, //hooks链表
updateQueue: {}, //effects链表
....
}
对于函数组件,我们现在只需要关注他这几个属性就行了
首先看到renderWithHooks函数,他是执行函数组件的方法。
function renderWithHooks(current, workInProgress, Component, props,secondArg, nextRenderLanes){
// 将当前fiber赋值给currentlyRenderingFiber,hooks执行的时候通过这个获取当前的fiber对象
currentlyRenderingFiber = workInProgress;
// 清除fiber上面的memoizedState和updateQueue,为啥呢?因为即将执行函数组件,
// 函数组件的hooks对象以链表形式存放在fiber.memoizedState
// 函数组件的useLayoutEffect和useEffect以effects链表的形式存放在fiber.updateQueue上
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// 赋值hooks对象
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 真正执行函数
let children = Component(props, secondArg);
// 重新赋值hooks对象。此时不能再调用hooks,会报错
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// 重新置空全局变量
currentlyRenderingFiber = (null: any); //指向当前的函数fiber
currentHook = null; //current树上的指向的当前调度的 hooks节点。
workInProgressHook = null; //workInProgress树上指向的当前调度的 hooks节点。
return children
}
如上,可以看出,renderWithHooks主要做了:
null上没有useState
等的错误。可以看到执行函数组件之后,又赋值了ContextOnlyDispatcher,这个其实就是用来抛出错误的,因为函数组件已经执行完毕了,不能再执行hooks了。现在来看这个demo。
const App2 = () => {
const [state, setState] = useState(1);
const stateMemo = useMemo(() => {
state + 1;
}, [state]);
const stateUseCallback = useCallback(() => {
return state + 1;
}, [state]);
const stateRef = useRef(state);
useEffect(
function useEffect() {
console.log("useEffect=====create");
return () => {
console.log("effect====destory");
};
},
[state]
);
useLayoutEffect(
function useLayoutEffect() {
console.log("useLayoutEffect=====create");
return () => {
console.log("useLayoutEffect====destory");
};
},
[state]
);
return (
setState(3)}>
state: {state}
<>memo: {stateMemo}>
callback: {stateUseCallback()}
);
};
每一个hooks都会创建一个hooks对象。结构基本一样
const Hook = {
queue: {}, //挂载着更新的操作,类似于类fiber.updateQueue
memoziedState: {}, //保存hook的状态,不同的hook保存的数据不同
baseState: {}, // 如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
baseQuuee: {}, //存放着因为优先级较低而跳过的hook
next: {} ,//指针,指向下一个hook
}
上面我们说过,fiber.memoizedState存放着hooks链表。
从demo debugger上,发现函数fiber是这样的
{
memoziedState: {
baseQueue: null
baseState: 1
memoizedState: 1,
queue: {},
next: {
baseQueue: null
baseState: null
memoizedState: (2) [2, Array(1)],
queue: null,
next: {
baseQueue: null
baseState: null
memoizedState: {current: 1}
next: {memoizedState: {…}, baseState: null, baseQueue: null, queue: null, next: {…}}
queue: null
}
}
}
}
可以看到fiber.memozedState就存放着hooks链表,每一个hook通过next指针相连。
useState的第一次执行的函数就是mountState
function mountState(initialState){
// 创建hooks对象
const hook = mountWorkInProgressHook();
//初始值是函数就执行
if (typeof initialState === 'function') {
initialState = initialState();
}
// initState存放到hook对象上的memoizedState和baseState
hook.memoizedState = hook.baseState = initialState;
// 创建queue对象,每个hooks都有一个queue对象,用来存放当前hooks产生的update等信息。
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null, //存放update链表
interleaved: null,
lanes: NoLanes, //优先级
dispatch: null,
lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说 useState是特殊的useReducer
// 上一次render时候的update
lastRenderedState: (initialState: any),
};
hook.queue = queue;
// 派发action的函数,通过bind传入了当前的fiber,和Queue对象
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber, //函数的fiber
queue,
);
// 返回初始化状态和dispatch
return [hook.memoizedState, dispatch];
}
可以看到mountState做了
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null, // useState中 保存 state 信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和 deps | useRef 中保存的是 ref 对象。
baseState: null, //usestate和useReducer中,一次更新中 ,产生的最新state值。
baseQueue: null, //usestate和useReducer中 保存最新的更新队列,存放着因为优先级比较低跳过的update链表
queue: null, //存放着本次更新相关的信息。
next: null, //指针
};
// 函数组件第一个hooks的时候,为空,通过workInProgressHook指针连接起整个函数组件的hooks链表。
if (workInProgressHook === null) {
// hooks链表第一个,挂载在fiber.memoizedState
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 插在上一个hook对象后面,形成链表
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
可以看到,它创建了hooks,并且将hooks挂载到了fiber.memoizedState上,以链表的形式。workInProgressHook就是用来执行当前的hook。
这个对象存放着更新的一系列值。
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null, //存放update链表
lanes: NoLanes, //优先级
dispatch: null, //存放着setState
lastRenderedReducer: basicStateReducer, //存放reducer,useState是指定的,而useReducer是自定义的,所以说useState是特殊的useReducer
// 上一次render时候的update
lastRenderedState: initialState, //用来给setState第一次调用的时候做优化。
};
我们现在知道useState初始化会创建hooks,初始化hook.queue,挂载到fiber.memoizedState上,然后返回hook.memoizedState和dispatch。
更新调用的是dispatch。从mountState可以看到。
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber, //函数的fiber
queue,
);
调用dispatchSetState,并且传入了fiber和queue。
function dispatchSetState( fiber, queue, action
const lane = requestUpdateLane(fiber);
// 创建update对象
const update: Update<S, A> = {
lane, //优先级
action, //值,对于useReducer来说是一个action
hasEagerState: false,
eagerState: null,
next: (null: any), //指针
};
// 插入当前update
enqueueUpdate(fiber, queue, update, lane);
// 获取workInprogress fiber
const alternate = fiber.alternate;
if( fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)){
// 让如果当前的fiber没有处于调度状态,那么第一次调用setState就会走优化逻辑。
const lastRenderedReducer = queue.lastRenderedReducer; //获取reducer
const currentState: S = (queue.lastRenderedState: any); // 当前的state
const eagerState = lastRenderedReducer(currentState, action); //获取最新的值
update.hasEagerState = true; //打标机,表示这个update已经计算过值了。
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 如果state没变,不调度
return;
}
}
const eventTime = requestEventTime(); //获取当前事件的执行时间
// 开启新的一轮调度。
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
可以看到dispatchSetState主要做了
总结:现在知道了setState会创建update,插入到hook.queue.pending上。然后调用scheduleUpdateOnFiber开启新的一轮调度。
接着我们看update阶段执行的useState,执行的是updateState函数。
// useState的update函数
function updateState<S>(
initialState: (() => S) | S,
): {
// 跟useReducer调用同样的函数,不过第一个
return updateReducer(basicStateReducer, (initialState: any));
}
这个updateState,其实也是调用updateReducer,从名字上可以看到,就是useReducer的更新函数,但是它默认传了basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
这个就是一个处理,如果setState传入的是普通值,那么reducer就直接返回,如果传入的是函数,那么就将当前的state传入,将返回值作为返回。这里也解释了为什么setState可以获取最新的State。我们来看updateReducer怎么计算新的state的。
function(reducer, initialArg){
// 通过current fiber的Hooks对象,一个一个复制对应的hooks返回
const hook = updateWorkInProgressHook();
// 获取更新队列
const queue = hook.queue;
queue.lastRenderedReducer = reducer; //赋值redcuer,每一次useReducer执行,都会重新赋值reducer
// ----------- 拼接跳过的update-----------
const current: Hook = (currentHook: any); //获取当前hooks的对应在current fiber上的hook
let baseQueue = current.baseQueue; //获取因为优先级较低而跳过的update链表
const pendingQueue = queue.pending; //获取当前的update任务链表
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;
}
// -----------处理update链表--------------
// 如果有跳过的Update,他们存放在了hooks.baseQueue上面,将他们取下来,然后插入到当前的update链表之前
//存在更新的链表。
if (baseQueue !== null) {
// ----- 定义一些变量存储值---------
// We have a queue to process.
const first = baseQueue.next; //获取第一额update
let newState = current.baseState; // 保存每一个update处理后得到的最新的state
let newBaseState = null;
//保存着这次调度之后,fiber的最新state,跟newState不一样的是,有些update因为优先级较低被跳过,所以newBaseState的值是停留在被跳过的update的state,为的就是保证状态不丢失
let newBaseQueueFirst = null; //新的需要跳过的update链表的第一个指针
let newBaseQueueLast = null; //新的需要跳过的update链表的最后一个指针
let update = first;
// do-while循环处理update
do {
// -----------优先级判断-----------------
const updateLane = update.lane; //优先级
if(!isSubsetOfLanes(renderLanes, updateLane)){
//如果当前的update优先级不够
//clone一个Update
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
// --- 将跳过的update以链表的形式放到newBaseQueueFirst------
if (newBaseQueueLast === null) { //这是第一个跳过的update
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
// 不是第一个跳过的,直接插在链表后面
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 更新队列中的剩余优先级。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
}else {
//如果这个update优先级够了
//-----为了保持状态的连续性,若update之前有别的update被跳过了,那么当前的update也得clone一份存入跳过的链表
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;
}
// 开始处理这个update了,这里对照着上面setState做的优化,如果值已经计算了,直接取update.eagerState,
if (update.hasEagerState) {
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
// 通过reducer计算最新的state,赋值给newState
// 这里每一次调用reducer,都会将最新计算得到的NewState传入进去,所以这也是为什么,setState就可以获取到最新的state的原因了。因为上一个update处理过的state,已经赋值给了newState了。
newState = reducer(newState, action);
}
}
// 处理下一个update
update = update.next;
} while (update !== null && update !== first); //直到所有的Update处理完毕
//----- 处理新的state了,如果有跳过的update,那么hook.memozedState就不能是最新的state。而是跳过的第一个update的时候的state
if (newBaseQueueLast === null) {
//如果没有跳过的update,当前hooks.baseState才是最新的state
newBaseState = newState;
} else {
// 跳过的update链表首尾相连
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// 将状态更新到hook对象上
hook.memoizedState = newState; //存放最新的state
hook.baseState = newBaseState; //如果有跳过的update,存放的就不是最新的state,而且跳过的update之前的state
hook.baseQueue = newBaseQueueLast; //baseQueue存在跳过的update链表
queue.lastRenderedState = newState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch]; //这里返回的状态并不是newState,而是newBaseState
}
如上,可以看到useState的update执行的函数做的事情还是很多的。
调用updateWorkInProgressHook获取到hooks对象
判断是否有跳过的update链表,有的话就跟现在的update链表拼接到一起处理。
递归处理update。如果优先级不够的,就先放着,优先级够的,还得判断他前面有没有跳过的update,有的话那么当前的update也得clone一份放入跳过的链表之中。然后处理update
处理的时候会判断当前update是否已经计算过了,不是的话,就会调用reducer将hook.baseState传入。注意,这里处理update链表的基础state是hook.baseState。而不是hook.memoizedState,这是因为,为了状态的连续性,不止优先级较低的update,后面的update都得存起来,而且计算update的state必须留在跳过的update的时候的hook.state才行。这里通过reducer函数处理action,也可以知道为什么setState传入函数就可以获取最新的state,因为上一个Update已经计算得到新的值,然后才传入reducer的。
接着更新hook,可以看到,如果有跳过的update,那么hook.baseState赋值的是newBaseState。而且只当有没调过的update的时候,才会执行newBaseState=newState
最后返回最新的到的hook.memoizedState。
这里我们需要注意的有两点,一个是updateWorkInProgressHook如何获取hooks。一个是优先级跳过update,react为了避免状态丢失做的处理。
function updateWorkInProgressHook(){
let nextCurrentHook: null | Hook;
if (currentHook === null) {
// 函数组件第一个hooks,为null。
// 获取current fiber
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
// 从curentFiber上面获取mount时候创建的hook对象,现在nextCurrentHooks已经有一条完整的当前current fiber的hooks链表了
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
// 第二次,直接从current fiber上往下取第二个hooks
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook; //获取workInprogress fiber下一个hook
if (workInProgressHook === null) {
// 函数组件第一个hooks执行的时候,为null
//update时候, 获取workInprogress fiber上面第一个hooks对象,应该为null
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
// 直接往workInprogress fiber的next下获取
nextWorkInProgressHook = workInProgressHook.next; //null
}
if (nextWorkInProgressHook !== null) {
// 已经有值了,将nextWorkInProgressHook赋值给workInProgressHook去返回,
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
// update 第一次进来应该为null
// 第一次进来nextCurrentHooks不应该为null
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
// 指向current fiber对应的hooks对象
currentHook = nextCurrentHook;
// 从Curent fiber上面复制一个hooks给workInprogress fiber
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// update第一次为null
if (workInProgressHook === null) {
// This is the first hook in the list.
// 将新创建的Hooks复制给wokrInprogress fiber的memoizedState,
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
// 返回新创建的hooks
return workInProgressHook;
}
这里的逻辑看似复杂,其实就做了一件事情。就是沿着current fiber.memoizedState开始,clone hooks对象,新创建一个newHook,赋值给workInProgressHook,并且以链表的形式存放在workInporgress fiber.memoizedState上面。
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
总结:就是从当前的current fiber上面的hook将属性复制过来,避免状态的丢失,因为baseQueue存放着的是上一次更新的链表。而queue存放着的就是此次更新存放的对应信息。
接着是第二个点,因为update优先级的概念,为了避免丢失,主要做了什么?
// do-while循环处理update
do {
// -----------优先级判断-----------------
const updateLane = update.lane; //优先级
if(!isSubsetOfLanes(renderLanes, updateLane)){
//如果当前的update优先级不够
//clone一个Update
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
// --- 将跳过的update以链表的形式放到newBaseQueueFirst------
if (newBaseQueueLast === null) { //这是第一个跳过的update
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
// 不是第一个跳过的,直接插在链表后面
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 更新队列中的剩余优先级。
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
}else {
//如果这个update优先级够了
//-----为了保持状态的连续性,若update之前有别的update被跳过了,那么当前的update也得clone一份存入跳过的链表
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;
}
// 开始处理这个update了,这里对照着上面setState做的优化,如果值已经计算了,直接取update.eagerState,
if (update.hasEagerState) {
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
// 通过reducer计算最新的state,赋值给newState
// 这里每一次调用reducer,都会将最新计算得到的NewState传入进去,所以这也是为什么,setState就可以获取到最新的state的原因了。因为上一个update处理过的state,已经赋值给了newState了。
newState = reducer(newState, action);
}
}
// 处理下一个update
update = update.next;
} while (update !== null && update !== first); //直到所有的Update处理完毕
主要做了两步操作,
这就是useState得一个整体流程了。
这两个hooks其实差不多,就是tag标识不同,执行时机不同而已。
直接看useEffect
function mountEffect(create, deps){
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,// useEffect得标识
create,
deps,
);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
// 创建Hooks
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags; //更新flags标记,commit阶段有用,表示有useEffect得副作用
// useEffect/useLayoutEffect的hook.memoizedState = effect。并且effect通过链表的形式挂载到了fiber.updateQueue.lastEffect上面
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}
可以看到useEffect得mount做了:
前置知识
useEffect得hooks对象:
{
memoizedState: {effects},
queue:null,
next: {}
}
看看pushEffect
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag, //layoutEffect和useEffect的标识
create, //创建函数
destroy, // 销毁函数
deps, //依赖项
next: (null: any), //effects会连接成为一个链表。
};
// 获取函数组件fiber.updateQueue
let componentUpdateQueue = currentlyRenderingFiber.updateQueue //获取fiber.updateQueue
if (componentUpdateQueue === null) {
// 如果是第一个effects
//创建一个有LastEffect的对象
componentUpdateQueue = createFunctionComponentUpdateQueue(); //其实就是 { lastEffect: null, stores: null,};
// 将这个对象赋值到fiber.updateQueue上面
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
// 接着将当前effect挂载到updateQueue.lastEffect上,以环状链表的形式
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 第二个effects之后
// 获取之前创建的updateQueue.lastEffect,他永远指向最后一个effects
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
//如果是空,自己形成一个环状链表
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
//否则,插入到上一个effects后面
const firstEffect = lastEffect.next; // lasEffect指向最后一个,那么.next就是第一个
lastEffect.next = effect; //插到最后一个后面
effect.next = firstEffect; //下一个指向第一个effects,形成环状
componentUpdateQueue.lastEffect = effect; //updateQueue.lastEffect永远指向最后一个effect
}
}
// 返回本次创建的effects
return effect;
}
其实做的事也简单:
effects对象
const effect: Effect = {
tag, //layoutEffect和useEffect的标识
create, //创建函数
destroy, // 销毁函数
deps, //依赖项
next: (null: any), //effects会连接成为一个链表。
};
effects对象,tag就是用来标识得,比如useEffect是9,而useLayoutEffect是5,其次就是保存了创建函数,依赖项等。next指针指向下一个effects。多个useEffect创建得effects也会以环状链表的形式,插入到fiber.updateQueue.lastEffect
这就是useEffect更新的时候做的事情。而useLayoutEffect是一样的操作,只不过标识换成了useLayoutEffect得标识。
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
let fiberFlags: Flags = UpdateEffect;
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
fiber.flags是updateEffect,标识是HookLayout。
接着看啥时候执行呢?第一次useLayoutEffect和第一次useEffect执行。
在before-mutation阶段之前,通过调度ScheduleCallback,以普通优先级,调度了useEffect执行函数。
function commitRootImp(){
.....
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
// 如果有useEffect得影响
if (!rootDoesHavePassiveEffects) {
// 赋值全局变量,表示有useEffect的副作用
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () => { // 以普通优先级调度useEffect
flushPassiveEffects();
// This render triggered passive effects: release the root cache pool
// *after* passive effects fire to avoid freeing a cache pool that may
// be referenced by a node in the tree (HostRoot, Cache boundary etc)
return null;
});
}
}
}
最终执行的就是flushPassiveEffects函数。他是以普通优先级异步调度得。
export function flushPassiveEffects(): boolean {
if (rootWithPendingPassiveEffects !== null) {
try {
setCurrentUpdatePriority(priority);
return flushPassiveEffectsImpl();
} finally {
}
}
return false;
}
可以看到,他会判断rootWithPendingPassiveEffects是不是不为null。是的话才会往下执行flushPassiveEffectsImpl函数。
那么rootWithPendingPassiveEffects是在哪里被赋值的呢?答案就是layout阶段之后。
在异步调度flushPassiveEffect的时候
rootDoesHavePassiveEffects = true;
执行了这个操作,然后接着执行,到了layout阶段之后,有这段代码
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) { // 有useEffect的effects
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
}
如果rootDoesHavePassiveEffects为true,那么就将FiberRoot赋值给rootWithPendingPassiveEffects。因为commit阶段是同步的,所以这一切都是在同一帧之内运行的。而等到flushPassiveEffectsImpl函数执行的时候,其实已经赋值了。
所以我们知道useEffect是在before-mutaiton阶段之前调度,在layout阶段之后赋值全局变量,在之后的某一帧执行。
function flushPassiveEffectsImpl(){
if (rootWithPendingPassiveEffects === null) {
return false;
}
const root = rootWithPendingPassiveEffects; //获取FiberRoot
// 重置全局变量
rootWithPendingPassiveEffects = null;
// 调用UseEffect的销毁函数
commitPassiveUnmountEffects(root.current); //传入rootFiber
// 调用useEffect函数
commitPassiveMountEffects(root, root.current); //传入FiberRoot rootFiber
}
调用了commitPassiveUnmountEffects和commitPassiveMountEffects函数
看看commitPassiveMountEffects
export function commitPassiveMountEffects(
root: FiberRoot, //FiberRoot
finishedWork: Fiber // rootFiber
) {
nextEffect = finishedWork; //将rootFiber赋值给nextEffect
commitPassiveMountEffects_begin(finishedWork, root);//调用commitPassiveMountEffects_begin
}
function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
// while循环找到最下面一个有flags的fiber
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
// 有useEffect的标识
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild; //while循环找到最下面一个有flags的fiber
} else {
commitPassiveMountEffects_complete(subtreeRoot, root); //将rootFiber和root赋值进去。此时nextEffect就是最下面的一个fiber
}
}
}
function commitPassiveMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot
) {
// while往上递归,处理所有有flags的fiber
while (nextEffect !== null) {
const fiber = nextEffect;
// 如果有passivede的flags,表示有useEffect的flags
if ((fiber.flags & Passive) !== NoFlags) {
commitPassiveMountOnFiber(root, fiber);
}
if (fiber === subtreeRoot) { //当循环到最上面一个rootFiber,直接退出
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
//有兄弟节点,退出,让兄弟节点执行commitPassiveMountEffects_begin的while循环,
// 找到兄弟节点下面所有的有useEffect标识的fiber
nextEffect = sibling;
return;
}
// 下一个执行while的就是父节点
nextEffect = fiber.return;
}
}
可以看到,上面几个方法主要就从rootFiber开始往下找,找到最下面一层的有useEffect标识的fiber,然后慢慢递归上来,就像beginWOrk和completeWork的逻辑。接着执行commitPassiveMountOnFiber
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot, //FiberRoot
finishedWork: Fiber //当前的要执行副作用函数的fiber
){
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// 执行useEffect的函数,标识是HookPassive | HookHasEffect
commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork) //传入标识和当前的fiber
break;
}
...
}
}
commitHookEffectListMount就是用来执行effects的函数。
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
// 获取任务队列, effects对象保存在函数组件的fiber.updateQueue.lastEffect之上。
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; //获取effects链表
if (lastEffect !== null) {
// 环状链表,next才是第一个
const firstEffect = lastEffect.next;
let effect = firstEffect;
// while循环,一个组件有多个useEffect,他们通过next指针连接。
// 通过循环依次执行
do {
// 根据effect的tag,layoutEffect是5,useEffect是9,layout阶段同步运行layoutEffect,异步调用useEffect
if ((effect.tag & flags) === flags) {
// Mount 执行useLayoutEffect/useEffect的create函数
const create = effect.create;
effect.destroy = create(); //将返回值赋值给effect.destroy属性
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
可以看到,
到这里我们就知道了useEffect是如何被执行的。那么销毁函数呢?
其实useEffect销毁函数的调用,跟useEffect的逻辑一样,只不过调用的是
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
finishedWork,
finishedWork.return
);
commitHookEffectListUnmount函数,传入useEffect标识。
看看逻辑
function commitHookEffectListUnmount(
flags: HookFlags, //flags
finishedWork: Fiber, // 当前fiber
nearestMountedAncestor: Fiber | null //父亲fiber
) {
// effects链表存放在fibe.updateQueue.lastEffect智商
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
// 获取第一个effect
const firstEffect = lastEffect.next;
let effect = firstEffect;
// while循环处理所有的effect
do {
// 通过tag和flags判断当前执行哪种类型的effects。
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy; //获取销毁函数
effect.destroy = undefined; //重置为undefined是因为useEffect更新时候重新执行create,会重新赋值给effects.destory
//调用销毁函数
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
可以看到逻辑是差不多的,都是从fiber.updateQueue.lastEffect上面获取effects链表,然后判断,最后执行destory函数,注意这有一个细节,每次destory都会置为undefined。是因为flushPassiveEffectsImpl函数,其实是调用销毁函数的执行的。
// 调用UseEffect的销毁函数
commitPassiveUnmountEffects(root.current); //传入rootFiber
// 调用useEffect函数
commitPassiveMountEffects(root, root.current); //传入FiberRoot
比如一次更新之后,销毁函数先执行,才会执行创建函数。而mount的时候,destory函数为undefined,所以就跳过了。
下一节我们继续看UseEffet更新的时候如何执行的。