React 实现 useLayoutEffect 与 useEffect

本文将带大家实现 useLayoutEffect 与 useEffect。

先看下如何使用。

js 复制代码
function FunctionComponent() {
    const [count1, setCount1] = useReducer((x) => x + 1, 0)
    const [count2, setCount2] = useState(0)
    
    useLayoutEffect(() => {
        console.log('useLayoutEffect')
    }, [count1])
    
    useEffect(() => {
        console.log('useEffect')
    }, [count2])
    
    return (
        <div className="border">
            <h1>函数组件</h1>
            <button onClick={() => setCount1()}>{count1}</button>
            <button onClick={() => setCount2(count2 + 1)}>{count2}</button>
        </div>
    )
}

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  (<FunctionComponent />) as any
);

两者对比

存储结构相同

存储结构都是单向循环链表。

执行时机不同

  • useLayoutEffect 会在所有的 DOM 变更之后同步调用 effect。
  • useEffect 会在组件渲染到屏幕之后延迟执行 effect。

useLayoutEffect 可能会影响性能,尽可能使用 useEffect。比如在 useLayoutEffect 增加 ajax 请求,由于是同步执行,可能会影响 DOM 的渲染。

实现 useLayoutEffect 与 useEffect

js 复制代码
function pushEffect(
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<any> | void | null
) {
  const effect: Effect = {
    tag: hookFlags,
    create,
    deps,
    next: null,
  };

  let componentUpdateQueue = currentlyRenderingFiber!.updateQueue;
  // 单向循环链表
  if (componentUpdateQueue === null) {
    // 第一个 effect
    componentUpdateQueue = {
      lastEffect: null,
    };
    currentlyRenderingFiber!.updateQueue = componentUpdateQueue;
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    const firstEffect = lastEffect.next;
    lastEffect.next = effect;
    effect.next = firstEffect;
    componentUpdateQueue.lastEffect = effect;
  }

  return effect;
}

// 存储 effect
function updateEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<any> | void | null
) {
  const hook = updateWorkInProgressHook();

  const nextDeps = deps === undefined ? null : deps;
  // 依赖项是否发生变化
  if (currentHook !== null) {
    if (nextDeps !== null) {
      const prevDeps = currentHook.memoizedState.deps;
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return;
      }
    }
  }

  currentlyRenderingFiber!.flags |= fiberFlags;
  // * 1. 保存 effect 2. 构建 effect 链表
  hook.memoizedState = pushEffect(hookFlags, create, nextDeps);
}

// useEffect 与 useLayoutEffect 的区别
// 存储结构一样
// effect 和 destroy 函数的执行时机不同
export function useLayoutEffect(
  create: () => (() => void) | void,
  deps: Array<any> | void | null
) {
  return updateEffectImpl(Update, HookLayout, create, deps);
}

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<any> | void | null
) {
  return updateEffectImpl(Passive, HookPassive, create, deps);
}

Commit 阶段

commitReconciliationEffects 增加更新的 case。

js 复制代码
function commitHookEffectListMount(hookFlags: HookFlags, finishedWork: Fiber) {
  const updateQueue = finishedWork.updateQueue;
  let lastEffect = updateQueue!.lastEffect;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag & hookFlags) === hookFlags) {
        const create = effect.create;
        create();
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

function commitReconciliationEffects(finishedWork: Fiber) {
  const flags = finishedWork.flags;
  if (flags & Placement) {
    // 页面初次渲染 新增插入 appendChild
    // todo 页面更新,修改位置 appendChild || insertBefore
    commitPlacement(finishedWork);
    finishedWork.flags &= ~Placement;
  }
  // 删除
  if (flags & ChildDeletion) {
    // parentFiber 是 deletions 的父 dom 对应的 fiber
    const parentFiber = isHostParent(finishedWork)
      ? finishedWork
      : getHostParentFiber(finishedWork);
    const parentDOM = parentFiber.stateNode;
    commitDeletions(finishedWork.deletions!, parentDOM);
    finishedWork.flags &= ~ChildDeletion;
    finishedWork.deletions = null;
  }

  // 更新
  if (flags & Update) {
    if (finishedWork.tag === FunctionComponent) {
      // 执行 layout effect
      commitHookEffectListMount(HookLayout, finishedWork);
      finishedWork.flags &= ~Update;
    }
  }
}

commitRoot 增加 passive effect阶段,执行 passive effect。

js 复制代码
function commitPassiveEffects(finishedWork: Fiber) {
  switch (finishedWork.tag) {
    case FunctionComponent: {
      if (finishedWork.flags & Passive) {
        commitHookEffectListMount(HookPassive, finishedWork);
        finishedWork.flags &= ~Passive;
      }
      break;
    }
  }
}

function recursivelyTraversePassiveMountEffects(finishedWork: Fiber) {
  let child = finishedWork.child;
  while (child !== null) {
    // 1. 遍历子节点,检查子节点
    recursivelyTraversePassiveMountEffects(child);
    // 2. 如果有 passive effects,执行~
    commitPassiveEffects(finishedWork);
    child = child.sibling;
  }
}

export function flushPassiveEffects(finishedWork: Fiber) {
  // 1. 遍历子节点,检查子节点
  recursivelyTraversePassiveMountEffects(finishedWork);
  // 2. 如果有 passive effects,执行~
  commitPassiveEffects(finishedWork);
}

function commitRoot(root: FiberRoot) {
  // 1. commit 阶段开始
  const prevExecutionContext = executionContext;
  executionContext |= CommitContext;
  // 2.1 mutation阶段, 渲染DOM树
  commitMutationEffects(root, root.finishedWork as Fiber); //Fiber,HostRoot=3
  // 2.2 passive effect阶段,执行 passive effect
  Scheduler.scheduleCallback(NormalPriority, () => {
    flushPassiveEffects(root.finishedWork as Fiber);
  });

  // 3. commit 结束
  executionContext = prevExecutionContext;
  workInProgressRoot = null;
}
相关推荐
单线程_014 小时前
从案例分析 Vue3 Tokenizer+Parser 源码五
前端·javascript·vue.js
mldong11 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排12 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby606112 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角13 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!13 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰14 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi15 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen16 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马16 小时前
AI源码分析:拆解模型底层实现逻辑
前端