了解react的整体流程,会有助于理解本文。
hooks是什么?
要了解hooks是什么,我们得先想知道react怎么执行函数组件。
先看看函数组件的fiber是什么?
const fiber = {
type: f App(){},
memoziedState: {},
updateQueue: {},
....
}
对于函数组件,我们现在只需要关注他这几个属性就行了
首先看到renderWithHooks函数,他是执行函数组件的方法。
function renderWithHooks(current, workInProgress, Component, props,secondArg, nextRenderLanes){
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
return children
}
如上,可以看出,renderWithHooks主要做了:
- 将当前调度的函数fiber赋值currentlyRenderingFiber,从名字可以看出,currentlyRenderingFiber就是当前调度的fiber,hooks执行的时候,通过全局变量currentlyRenderingFiber获取到当前的fiber。
- 清除fiber上面一些属性,如fiber.memoizedState和fiber.updateQueue。fiber.memoizedState存放着hooks链表,指向第一个hooks。而updateQueue存放着useEffect/useLayoutEffect产生的effects。为什么这里要清除呢?因为函数组件即将执行,每一次重新执行函数组件,都会重新生成effects链表和hooks链表。
- 赋值ReactCurrentDispatcher.current属性,我们调用的hooks实际上就是通过ReactCurrentDispatcher.current获取到的,在一开始是null。所以在函数组件外部执行的时候会报
null上没有useState 等的错误。可以看到执行函数组件之后,又赋值了ContextOnlyDispatcher,这个其实就是用来抛出错误的,因为函数组件已经执行完毕了,不能再执行hooks了。 - 执行函数组件,可能会执行各种hooks。
- 执行完函数组件之后,需要重新赋值全局变量,比如currentlyRenderingFiber置为null。因为要轮到下一个函数fiber调度了。curretnHook置为null。这个全局变量其实就是用来指向current fiber上的hooks节点,通过currentHook,复制出一个新的workInprogress hook。workInporgressHook跟currentHook一样,不过它指向的是wokrInporgress hook。
- 返回children
现在来看这个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 (
<div onClick={() => setState(3)}>
<div>state: {state}</div>
<div><>memo: {stateMemo}</></div>
<div>callback: {stateUseCallback()}</div>
</div>
);
};
预知识:
每一个hooks都会创建一个hooks对象。结构基本一样
const Hook = {
queue: {},
memoziedState: {},
baseState: {},
baseQuuee: {},
next: {} ,
}
上面我们说过,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
useState的第一次执行的函数就是mountState
function mountState(initialState){
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
);
return [hook.memoizedState, dispatch];
}
可以看到mountState做了
- 调用mountWorkInProgressHook创建hooks对象。
- 如果初始值是函数,就执行。
- 更新hooks属性
- 创建queue对象。
- 创建dispatch函数,就是他来派发action。
先看看mountWorkInProgressHook
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
可以看到,它创建了hooks,并且将hooks挂载到了fiber.memoizedState上,以链表的形式。workInProgressHook就是用来执行当前的hook。
queue对象
这个对象存放着更新的一系列值。
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
我们现在知道useState初始化会创建hooks,初始化hook.queue,挂载到fiber.memoizedState上,然后返回hook.memoizedState和dispatch。
更新的时候
更新调用的是dispatch。从mountState可以看到。
const dispatch: Dispatch = queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
);
调用dispatchSetState,并且传入了fiber和queue。
function dispatchSetState( fiber, queue, action
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
enqueueUpdate(fiber, queue, update, lane);
const alternate = fiber.alternate;
if( fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)){
const lastRenderedReducer = queue.lastRenderedReducer;
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
}
const eventTime = requestEventTime();
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
可以看到dispatchSetState主要做了
- 创建update,每一个更新都会创建对应的update对象。
- 调用enqueueUpdate将update插入hook.queue.pending上,以链表的形式。
- 判断是否处于调度状态,如果不是,当前的setState就可以直接计算值了。然后判断计算出来的值跟之前的state是否相等,相等则不调度,这也是函数的setState跟来组件的this.setState的区别。这里也不用担心说,万一计算出的值变了,那不还得继续调度,在这里如果计算值之后,会将update.hasEagerState置为true,表示已经计算过了,那么等调度完处理update的时候,就不会重新计算,而且直接取update.eagerState最后更新后的值。
- 调用scheduleUpdateOnFiber开启新的一轮调度。
总结:现在知道了setState会创建update,插入到hook.queue.pending上。然后调用scheduleUpdateOnFiber开启新的一轮调度。
接着我们看update阶段执行的useState,执行的是updateState函数。
function updateState<S>(
initialState: (() => S) | S,
): {
return updateReducer(basicStateReducer, (initialState: any));
}
这个updateState,其实也是调用updateReducer,从名字上可以看到,就是useReducer的更新函数,但是它默认传了basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
这个就是一个处理,如果setState传入的是普通值,那么reducer就直接返回,如果传入的是函数,那么就将当前的state传入,将返回值作为返回。这里也解释了为什么setState可以获取最新的State。我们来看updateReducer怎么计算新的state的。
function(reducer, initialArg){
const hook = updateWorkInProgressHook();
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;
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,
);
}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;
}
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);
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
如上,可以看到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为了避免状态丢失做的处理。
updateWorkInProgressHook
function updateWorkInProgressHook(){
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
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 {
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,
);
}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;
}
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);
主要做了两步操作,
- 第一步:如果是跳过的update,就要将其保存起来,并且他之后所有的update(即使是优先级高的)也必须clone一个保存起来,防止update之间有依赖性。
- 第二步:如果有跳过的update,那么下次计算得state,不能是最新的state,必须是在哪里跳过,那会的state是如何得,下次就要用那会得state
这就是useState得一个整体流程了。
useEffect & useLayoutEffect
这两个hooks其实差不多,就是tag标识不同,执行时机不同而已。
直接看useEffect
function mountEffect(create, deps){
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
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,
);
}
可以看到useEffect得mount做了:
- 创建hooks
- 将fiber.flags打上标记。
- 调用pushEffect,他会返回effects对象,赋值给fiber.memoizedState
前置知识
useEffect得hooks对象:
{
memoizedState: {effects},
queue:null,
next: {}
}
看看pushEffect
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
next: (null: any),
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
其实做的事也简单:
- 创建effects对象。
- 将effects对象插入到fiber.updateQueue.lastEffect之上,以环状链表的形式。
effects对象
const effect: Effect = {
tag,
create,
destroy,
deps,
next: (null: any),
};
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执行。
useEffect执行
在before-mutation阶段之前,通过调度ScheduleCallback,以普通优先级,调度了useEffect执行函数。
function commitRootImp(){
.....
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
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) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
}
如果rootDoesHavePassiveEffects为true,那么就将FiberRoot赋值给rootWithPendingPassiveEffects。因为commit阶段是同步的,所以这一切都是在同一帧之内运行的。而等到flushPassiveEffectsImpl函数执行的时候,其实已经赋值了。
所以我们知道useEffect是在before-mutaiton阶段之前调度,在layout阶段之后赋值全局变量,在之后的某一帧执行。
接着看flushPassiveEffectsImpl函数是如何调用useEffect的
function flushPassiveEffectsImpl(){
if (rootWithPendingPassiveEffects === null) {
return false;
}
const root = rootWithPendingPassiveEffects;
rootWithPendingPassiveEffects = null;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
调用了commitPassiveUnmountEffects和commitPassiveMountEffects函数
看看commitPassiveMountEffects
export function commitPassiveMountEffects(
root: FiberRoot,
finishedWork: Fiber
) {
nextEffect = finishedWork;
commitPassiveMountEffects_begin(finishedWork, root);
}
function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
while (nextEffect !== null) {
const fiber = nextEffect;
const firstChild = fiber.child;
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else {
commitPassiveMountEffects_complete(subtreeRoot, root);
}
}
}
function commitPassiveMountEffects_complete(
subtreeRoot: Fiber,
root: FiberRoot
) {
while (nextEffect !== null) {
const fiber = nextEffect;
if ((fiber.flags & Passive) !== NoFlags) {
commitPassiveMountOnFiber(root, fiber);
}
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if (sibling !== null) {
ensureCorrectReturnPointer(sibling, fiber.return);
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
可以看到,上面几个方法主要就从rootFiber开始往下找,找到最下面一层的有useEffect标识的fiber,然后慢慢递归上来,就像beginWOrk和completeWork的逻辑。接着执行commitPassiveMountOnFiber
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,
finishedWork: Fiber
){
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork)
break;
}
...
}
}
commitHookEffectListMount就是用来执行effects的函数。
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
可以看到,
- 首先从当前的fiber上面获取updateQueue.lastEffect,他存放着effects链表
- 接着do while循环处理每一个effects,这里会做判断,因为effects链表有useEffect和useLayoutEffect的,他们之间通过tag来标识不同。这里我们传入的是useEffect的标识,所以只有useEffect的effects对象才会被执行。执行create函数后,将返回值赋值给destory函数。这样就完成了一个useEffect的执行了。
- 这个commitHookEffectListMount其实也是执行useLayoutEffect的函数,只不过执行useLayoutEffect的时候传入的标识是layoutEffect的标识。
到这里我们就知道了useEffect是如何被执行的。那么销毁函数呢?
其实useEffect销毁函数的调用,跟useEffect的逻辑一样,只不过调用的是
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
finishedWork,
finishedWork.return
);
commitHookEffectListUnmount函数,传入useEffect标识。
看看逻辑
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null
) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
const destroy = effect.destroy;
effect.destroy = undefined;
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
可以看到逻辑是差不多的,都是从fiber.updateQueue.lastEffect上面获取effects链表,然后判断,最后执行destory函数,注意这有一个细节,每次destory都会置为undefined。是因为flushPassiveEffectsImpl函数,其实是调用销毁函数的执行的。
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
比如一次更新之后,销毁函数先执行,才会执行创建函数。而mount的时候,destory函数为undefined,所以就跳过了。
下一节我们继续看UseEffet更新的时候如何执行的。
|