关于javascript:React-useEffect

筹备 – 知识点

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…

https://xiaoxiaosaohuo.github…

https://devrsi0n.com/articles…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理