【JS】React useEffect

React useEffect

D_Q_发布于 今天 08:18

准备 - 知识点

1、 filber 树

function App() {

return (

<div>

i am

<span>KaSong</span>

</div>

)

}

ReactDOM.render(<App />, document.getElementById("root"));

【JS】React useEffect

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树

【JS】React useEffect

  • 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 的执行时机

【JS】React useEffect

  • 忘记官网这句话。。。实际应用中如果这样理解可能会带来一些问题。。。

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,

);

}

【JS】React useEffect

  • 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;

}

}

}

【JS】React useEffect

【JS】React useEffect

【JS】React useEffect

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...

https://xiaoxiaosaohuo.github...

https://devrsi0n.com/articles...

javascript前端react.js

阅读 129发布于 今天 08:18

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

D_Q_

前端萌萌哒

448 声望

11 粉丝

0 条评论

得票时间

avatar

D_Q_

前端萌萌哒

448 声望

11 粉丝

宣传栏

准备 - 知识点

1、 filber 树

function App() {

return (

<div>

i am

<span>KaSong</span>

</div>

)

}

ReactDOM.render(<App />, document.getElementById("root"));

【JS】React useEffect

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树

【JS】React useEffect

  • 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 的执行时机

【JS】React useEffect

  • 忘记官网这句话。。。实际应用中如果这样理解可能会带来一些问题。。。

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,

);

}

【JS】React useEffect

  • 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;

}

}

}

【JS】React useEffect

【JS】React useEffect

【JS】React useEffect

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...

https://xiaoxiaosaohuo.github...

https://devrsi0n.com/articles...

以上是 【JS】React useEffect 的全部内容, 来源链接: utcz.com/a/111628.html

回到顶部