准备 - 知识点
1、 filber 树
function App() {
return (
<div>
i am
<span>KaSong</span>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
2、双缓存
- 在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。
- React应用的根节点通过current指针在不同Fiber树的rootFiber间切换来实现Fiber树的切换。
- 当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。
- 每次状态更新都会产生新的workInProgress Fiber树,通过current与workInProgress的替换,完成DOM更新。
3、位运算
- React 源码中应用了大量的位运算判断
科普文档:https://juejin.cn/post/684490...
4、2个阶段
- Render (深度遍历)
beginWork
- Mount -> 生成fiber树
- Update -> 更新fiber树
effectTag
- 标示节点的操作类型(删除、更新、插入等)16位的2进制数
completeWork
- Mount 由子到父 创建dom(虚拟) 并插入父级, 直到根节点rootFiber打上 Placement effectTag 一次行完成整个dom的插入
完成虚拟dom的插入更新 workInPeogress.updateQueue存放props
effectList
- 在completeWork 中将所有带有effectTag(可以理解为更新标识)的filber 节点放入 effect单向链表中
- Commit 阶段
- 不可中断
- 完成渲染工作- 执行副作用
useEffect
- 该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
- 赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。
- effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。
effect 的执行时机
- 忘记官网这句话。。。实际应用中如果这样理解可能会带来一些问题。。。
useEffect
- 与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。
useLayoutEffect
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
尽可能使用标准的 useEffect 以避免阻塞视觉更新。
https://codesandbox.io/s/opti...:395-425
https://codesandbox.io/s/hung...
effect return 的执行机制
useEffect return 的在什么时期执行呢?
- 组件在节点中消失(卸载)
- useEffect 执行
https://codesandbox.io/s/opti...
useEffect 为什么依赖项,不填写 依然可以拿到最新的state?
- 无论依赖项是否变化 每次重新push create函数(我们useEffect中的callback)
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var destroy = undefined;
if (currentHook !== null) {
// 获取当前effect节点所在队列位置
var prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
var prevDeps = prevEffect.deps;
// 判断前后的 deps 是否相同
if (areHookInputsEqual(nextDeps, prevDeps)) {
//创建一个新的 Effect,并把它添加到更新队列
//tag标记NoEffect$1 = 0
pushEffect(NoEffect$1, create, destroy, nextDeps);
return;
}
}
}
// 如果状态发生变化,则将当前effect的tag设置UnmountPassive | MountPassive,并后续在commitHookEffectList触发更新
sideEffectTag |= fiberEffectTag;
hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
}
- 可以看到updateLayoutEffect 的实现和 updateEffect 的实现一样。只是传入的tag不同。 fiberEffectTag和hookEffectTag会在commit阶段做对比,决定effect的执行时机。
源码断点分析
import { React, useCallback, useState, useEffect } from "../../CONST";
import BatchCount from "./BatchCount";
const asyncTime = new Promise((resolve, reject) => {
resolve();
});
export default function BatchState() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClick = useCallback(() => {
asyncTime.then(() => {
setCount1((count1) => {
console.log("+1 = ", count1);
return count1 + 1;
});
setCount1((count1) => {
console.log("+2 = ", count1);
return count1 + 2;
});
});
// setCount1((count1) => {
// console.log("+1 = ", count1);
// return count1 + 1;
// });
// setCount1((count1) => {
// console.log("+2 = ", count1);
// return count1 + 2;
// });
}, [count1, count2]);
useEffect(() => {
console.log("dep null");
});
useEffect(() => {
console.log("effect []]");
return () => {
console.log("return []")
}
}, []);
useEffect(() => {
console.log("effect1");
return () => {
console.log("return effect1")
}
}, [count1]);
useEffect(() => {
console.log("effect2");
return () => {
console.log("return effect2")
}
}, [count1]);
// console.log('render', {count1, count2})
return (
<div>
<button onClick={handleClick}>batch add</button>
<BatchCount count1={count1} count2={count2} />
</div>
);
}
- push
- componentUpdateQueue 存放所有 effect hooks 链表
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
// Circular
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
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 {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
- mount时候
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.effectTag |= fiberEffectTag;
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create,
undefined,
nextDeps,
);
}
- Update
- currentHook 之前的side effect hooks
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookEffectTag, create, destroy, nextDeps);
return;
}
}
}
HookLayout | HookHasEffect // 2 | 1 = 3
- 执行阶段
function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
这里要说的是 _useEffect_ 会进入异步调度流程
在commitRoot有这样的一个判断
if (firstEffect !== null && rootWithPendingPassiveEffects !== null) {
//这个commit包含passive effect,他们不需要执行直到下一次绘制之后,调度一个回调函数在一个异步事件中执行他们
var callback = commitPassiveEffects.bind(null, root, firstEffect);
if (enableSchedulerTracing) {
callback = unstable_wrap(callback);
}
passiveEffectCallbackHandle = unstable_runWithPriority(unstable_NormalPriority, function () {
return schedulePassiveEffects(callback);
});
passiveEffectCallback = callback;
}
参考
https://juejin.cn/post/684490...
https://react.iamkasong.com/h...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。