Kun

Kun

IT学徒、技术民工、斜杠青年,机器人爱好者、摄影爱好 PS、PR、LR、达芬奇潜在学习者


共 243 篇文章


​ 开新坑,看源码。

​ 在新公司帮别人重构了两次代码,发现改(吐槽)别人的代码还是蛮有意思的一件事。正好需要找一些社区的最佳实践,于是过来看一些源码

React

Hooks

https://mp.weixin.qq.com/s/aEfkRrSNymsiXzdvpNRm5Q

源码分析库:https://github.com/7kms/react-illustration-series

useEffect和uselayouteffect

组件加载时

export const UpdateEffect = /*  */ 0b000000000000100; // 4
export const PassiveEffect = /*  */ 0b000001000000000; // 512
export const HookHasEffect = /* */ 0b001; // 1
export const HookLayout = /*    */ 0b010; // 2
export const HookPassive = /*   */ 0b100; // 4

function mountEffect(create, deps) {
    return mountEffectImpl(UpdateEffect | PassiveEffect, HookPassive, create, deps);
}

function mountLayoutEffect(create, deps) {
    return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
}

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
    const hook = mountWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    currentlyRenderingFiber.effectTag |= fiberEffectTag; // --> 2 |= 516  /  2 |= 4
    hook.memoizedState = pushEffect(HookHasEffect | hookEffectTag, create, undefined, nextDeps);
}

组件更新时

function updateEffect(create, deps) {
    return updateEffectImpl(UpdateEffect | PassiveEffect, HookPassive, create, deps);
}

function updateLayoutEffect(create, deps) {
    return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
    const hook = updateWorkInProgressHook();
    const nextDeps = deps === undefined ? null : deps;
    let destroy = undefined;

    if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        // 1、拿到上一次 Effect 回调函数执行后返回的销毁函数(如果有的话)
        destroy = prevEffect.destroy;
        // 2、比较依赖项是否发生变化,如果相等,不需要重新触发回调,此时调用pushEffect创建一个effect加入到fiber.updateQueue上
        if (nextDeps !== null) {
            const prevDeps = prevEffect.deps;
            if (areHookInputsEqual(nextDeps, prevDeps)) {
                pushEffect(hookEffectTag, create, destroy, nextDeps);
                return;
            }
        }
    }

    // 3、如果依赖不相等,标记 effectTag,并调用 pushEffect 创建 effect 并作为 hook.memoizedState
    currentlyRenderingFiber.effectTag |= fiberEffectTag;
    hook.memoizedState = pushEffect(
        HookHasEffect | hookEffectTag,
        create,
        destroy,
        nextDeps,
    );
}

function areHookInputsEqual(nextDeps, prevDeps) {
    if (prevDeps === null) {
        return false;
    }
    for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
        if (is(nextDeps[i], prevDeps[i])) { // Object.is 比较两个值是否相等
            continue;
        }
        return false;
    }
    return true;
}

这两个 Effect 的实现原理,其实就是在组件 Render 阶段将 hook 对应的 effect 绑定到组件 Fiber 的 updateQueue 更新队列之上,并给组件标记 effectTag

其实在 commit 阶段,会对这两个 hook 的回调执行调用。由于 commit 阶段又分为三个小阶段:before Mutation、Mutation、Layout三个阶段

Mutation阶段只针对于 Hooks 中的 useLayoutEffect 处理

对于 useLayoutEffect hook,以同步的方式执行。

  • 在 commit - Mutation 阶段会遍历执行所有的 useLayoutEffect hook 的销毁函数(更新渲染阶段);
  • 在 commit - Layout 阶段会遍历执行所有的 useLayoutEffect hook 的回调函数(初渲染和更新渲染阶段,更新渲染阶段执行的前提是满足依赖项不相同)

对于 useEffect hook,以异步调度的方式执行。

  • 在 commit - beforeMutation 阶段调用 scheduleCallback 将 flustPassiveEffects 作为回调,开启一次异步调度 useEffect 的请求;
  • 在 commit - layout 阶段将需要执行的 useEffect 加入到这两个全局变量中:pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount;
  • 在 commit - layout 阶段之后,将 root(effectList) 保存到全局变量 rootWithPendingPassiveEffects 上;
  • 当 scheduleCallback 执行 flustPassiveEffects 时,执行 useEffect 的销毁函数、useEffect 的回调函数。

https://juejin.cn/post/6951754466009825310#heading-3

Antd

https://github.com/zhangzewei/read-antd-code

http://blog.html-js.site/2018/03/08/React%E7%BB%84%E4%BB%B6%E5%BA%93%E5%BC%80%E5%8F%91-%E5%A4%9A%E5%B1%82%E5%B5%8C%E5%A5%97%E5%BC%B9%E5%B1%82%E7%BB%84%E4%BB%B6/

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github