useEffect
- hooks,可以在函数组件中模拟出类组件的生命周期
- 如下:
- componentDidMount 组件挂载完成
- componentDidUpdate 更新完成
- componentWillUnmount 组件卸载
- 如何使用
- 接收两个值:create(函数)、deps(数组或undefined)
- create返回的值为destroy,也是一个函数,会在销毁周期中执行
- 当deps中的值发生改变,执行create函数
const [num,setNum] = useState(0);
useEffect(()=>{
console.log('执行1')
return ()=>{
console.log('销毁1')
}
},[num])
useEffect(()=>{
console.log('执行2')
return ()=>{
console.log('销毁2')
}
},[num])
源码
- hooks是函数组件独有功能,函数组件在初始化构建fiber树的时候会走
renderWithHooks
函数
- 在
renderWithHooks
中将react的hook赋值,具体规则与useReducer一致
- 相对于useReducer
增加了一个新值
,会在fiber的updateQueue种存储effect链表
- 因为fiber结构是会被复用的,而函数通常会多次执行,那么
每次执行的时候
,都需要将updateQueue清空
,防止一直堆积
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
workInProgress.updateQueue = null;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
首次挂载
- useEffect同样分为
初次挂载和更新
- 同样对应两套函数
- 在
react中只是在函数执行的时候透传参数,所以不罗列代码了
初始化effect实例
const HooksDispatcherOnMount = {
useReducer: mountReducer,
...
useEffect: mountEffect,
};
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, undefined, nextDeps);
}
- 先通过
mountWorkInProgressHook
初始化一个hooks实例,并将其关联到函数组件fiber的hooks链表上
- 判断deps是否存在,undefined就是没有
- 给fiber累加副作用,
PassiveEffect
为1024,HookPassive
为8
- 然后将
这个hook实例的memoizedState
指向pushEffect的结果
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null
}
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null
}
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;
}
pushEffect
做了两件事:
- 初始effect实例
- 将
effect实例添加到updateQueue上
,同样是单向循环链表
,lastEffect始终指向最后一个effect实例
- 然后返回effect实例
首次的effect并没有收集到destroy
,因为destroy是create的执行结果,目前我们还并没有执行它,只是将其转换成effect实例
执行effect函数
- 开头有讲到,
effect的执行时机
是在组件挂载完成
和组件销毁
- 那么它的执行逻辑就在dom的挂载逻辑中,也就是
CommitRoot
函数里
function commitRoot(root) {
const { finishedWork } = root;
if ((finishedWork.subtreeFlags & Passive) !== NoFlags
|| (finishedWork.flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffect) {
rootDoesHavePassiveEffect = true;
scheduleCallback(flushPassiveEffect);
}
}
console.log('开始commit~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
const subtreeHasEffects = (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
commitMutationEffectsOnFiber(finishedWork, root);
if (rootDoesHavePassiveEffect) {
rootDoesHavePassiveEffect = false;
rootWithPendingPassiveEffects = root;
}
}
root.current = finishedWork;
}
function flushPassiveEffect() {
console.log('下一个宏任务中flushPassiveEffect~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
if (rootWithPendingPassiveEffects !== null) {
const root = rootWithPendingPassiveEffects;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current);
}
}
finishedWork
就是我们本次要渲染的fiber树
fiber的副作用会向上冒
,那么它的父级
就可以通过subtreeFlags
统计副作用
- 那么只需要
判断顶层的副作用是否有1024
就可以得到后代节点中是否有effect要执行
rootDoesHavePassiveEffect为全局变量
,默认为false,我们把它当做一个开关
- 如果有effect要执行,打开开关
- 然后注册一个
异步事件
,在异步事件中去执行effect链表的执行
- 然后渲染完成dom后关闭开关,并且给全局变量
rootWithPendingPassiveEffects
赋值为根实例
- 注册的effect因为是异步事件,所以它的执行时机晚于dom渲染
开始执行
- 走的是
flushPassiveEffect
函数
- 假设effect是存在返回函数的,那么它的执行顺序应该为
先执行所有destroy函数
,再执行所有create函数
- 那么逻辑就拆分成两部分
- 执行destroy的函数
commitPassiveUnmountEffects
- 执行create的函数
commitPassiveMountEffects
commitPassiveUnmountEffects
- 通过
递归的形式
去依次遍历fiber
,然后找到函数fiber上的updateQueue
,执行effect实例上的destroy
- 有一个误区,它并不是找到所有destroy然后一次性执行,而是
找到一个函数fiber就执行它队列中的destory
- 查找顺序:
儿子->儿子的sibling->父
export function commitPassiveUnmountEffects(finishedWork) {
commitPassiveUnmountOnFiber(finishedWork);
}
function commitPassiveUnmountOnFiber(finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (flags & Passive) {
commitHookPassiveUnmountEffects(finishedWork, HookHasEffect | HookPassive);
}
break;
}
}
}
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
function commitHookPassiveUnmountEffects(finishedWork, hookFlags) {
commitHookEffectListUnmount(hookFlags, finishedWork);
}
function commitHookEffectListUnmount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
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;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect)
}
}
const A = () => {
useEffect(() => {
console.log("A");
return () => {
console.log("销毁A");
};
});
return <div>A</div>;
};
const B = () => {
useEffect(() => {
console.log("B");
return () => {
console.log("销毁B");
};
});
return <div>B</div>;
};
const App = () => {
const [num, setNum] = useState(0);
useEffect(() => {
console.log("App");
return () => {
console.log("销毁App");
};
});
return (
<div>
<button
onClick={() => {
setNum(num + 1);
}}
>
按钮
</button>
<A />
<B />
</div>
);
};
- 执行顺序:
- 首次执行:
A->B->APP
- 点击按钮,触发视图更新:
销毁A->销毁B->销毁APP->A->B->APP
commitPassiveMountEffects
- 执行create,与销毁执行顺序和逻辑几乎一致
- 唯一的区别在于会将
create函数的执行结果
放到effect实例的destory
上
- 还有一个点:
export function commitPassiveMountEffects(root, finishedWork) {
commitPassiveMountOnFiber(root, finishedWork);
}
function commitPassiveMountOnFiber(finishedRoot, finishedWork) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostRoot: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
break;
}
case FunctionComponent: {
recursivelyTraversePassiveMountEffects(finishedRoot, finishedWork);
if (flags & Passive) {
commitHookPassiveMountEffects(finishedWork, HookHasEffect | HookPassive);
}
break;
}
}
}
function recursivelyTraversePassiveMountEffects(root, parentFiber) {
if (parentFiber.subtreeFlags & Passive) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(root, child);
child = child.sibling;
}
}
}
function commitHookPassiveMountEffects(finishedWork, hookFlags) {
commitHookEffectListMount(hookFlags, finishedWork);
}
function commitHookEffectListMount(flags, finishedWork) {
const updateQueue = finishedWork.updateQueue;
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树,那么还是要走renderWithHooks
给react的钩子引用先换一套更新逻辑
清除updateQueue
const HooksDispatcherOnUpdate = {
useReducer: updateReducer,
...
useEffect: updateEffect,
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(HookHasEffect | hookFlags, create, destroy, nextDeps);
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null)
return null;
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (Object.is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
- 还是先复用老hook实例
- 执行步骤与初次并无太大区别
- 唯一的区别在于会进行新旧deps比较,
- 如果相同,就将本次的effect的tag设置为8,
初始化的时候我们设置的是9
- 那么走上边的CommitRoot后,走effect事件执行,
- 在遍历updateQueue的时候,我们传入的校验比对也是9
- 如果9===9,这跟初次一样,执行函数
- 那么如果deps相同,我们这次就将effect的tag设置为8了,那么显然是不同的,所以也就不会执行create和destroy了