主题
实现useEffect工作流程
本节课代码地址(请参考课程获取) 实现
useEffect需要考虑的:effect数据结构effect的工作流程如何接入现有流程
effect数据结构
什么是effect ?
js
function App() {
useEffect(() => {
// create
return() => {
// destroy
}
}, [xxx, yyy])
useLayoutEffect(() => {})
useEffect(() => {}, [])
// ...
}数据结构需要考虑: 不同effect 可以共用同一个机制
useEffect
useLayoutEffect
useInsertionEffect需要能保存依赖 需要能保存create 回调 需要能保存destroy 回调 需要能够区分是否需要触发create 回调
mount 时依赖变化时
const effect = {
tag,
create,
destroy,
deps,
next
}注意区分本节课新增的3个flag : 对于fiber ,新增PassiveEffect ,代表「当前fiber本次更新存在副作用」 对于effect hook ,Passive 代表「useEffect对应effect」 对于effect hook ,HookHasEffect 代表「当前effect本次更新存在副作用」 为了方便使用,最好和其他effect 连接成链表
render 时重置effect 链表effect的工作流程
调度副作用 调度需要使用Scheduler(调度器),调度器也属于React项目下的模块。在本课程中,我们不 会实现调度器,但会教如何使用它。
bash
pnpm i -w scheduler
pnpm i -D -w @types/scheduler收集回调 回调包括两类:
create 回调
destroy 回调在线Demo地址
jsx
function App() {
const [num, updateNum] = useState(0);
useEffect(() => {
console.log('App mount');
}, []);
useEffect(() => {
console.log('num change create', num);
return() => {
console.log('num change destroy', num);
};
}, [num]);
return (
<div onClick={() => updateNum(num + 1)}>
{num === 0 ? <Child /> : 'noop'}
</div>
);
}
function Child() {
useEffect(() => {
console.log('Child mount');
return() =>console.log('Child unmount');
}, []);
return 'i am child';
}这意味着我们需要收集两类回调: unmout 时执行的destroy 回调 update 时执行的create 回调 执行副作用 本次更新的任何create 回调都必须在所有上一次更新的destroy 回调执行完后再执行。 整体执行流程包括:
- 遍历
effect - 首先触发所有
unmount effect,且对于某个fiber,如果触发了unmountdestroy,本次更新不会再触发update create - 触发所有上次更新的
destroy - 触发所有这次更新的
createmount、update时的区别mount时:一定标记PassiveEffectupdate时:deps变化时标记PassiveEffect
纠错 useEffect 回调函数的执行依赖于:
1. 调度flushPassiveEffects 执行- 收集需要执行的回调函数(
commitMutationEffects方法) 其中1需要判断PassiveMask,当前2只判断了MutationMask,这导致useEffect deps变化后无法触发回调,所以2也需要增加PassiveMask的判断:
// 增加PassiveMask
const subtreeHasEffect =
(finishedWork.subtreeFlags & (MutationMask | PassiveMask)) !== NoFlags;
const rootHasEffect =
(finishedWork.flags & (MutationMask | PassiveMask)) !== NoFlags;详见fix: useEffect回调不收集的情况