当前位置: 首页 > news >正文

React16源码: React中commit阶段的commitAllLifeCycles的源码实现

commitAllLifeCycles


1 )概述

  • 在 react commit 阶段的 commitRoot 第三个 while 循环中
  • 处理了生命周期相关的一些内容
  • 它这个方法的名字叫做 commitAllLifeCycles

2 )源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L479

查看 commitAllLifeCycles

function commitAllLifeCycles(finishedRoot: FiberRoot,committedExpirationTime: ExpirationTime,
) {if (__DEV__) {ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();ReactStrictModeWarnings.flushLegacyContextWarning();if (warnAboutDeprecatedLifecycles) {ReactStrictModeWarnings.flushPendingDeprecationWarnings();}}// 通过遍历 firstEffect 到 lastEffect 的单项链表,基于每一个effect对应的fiber对象,对它做出对应的操作while (nextEffect !== null) {const effectTag = nextEffect.effectTag;// 这边的操作设置的是 update 和 callback ,如果有 Update | Callback 就执行 commitLifeCyclesif (effectTag & (Update | Callback)) {recordEffect();const current = nextEffect.alternate;commitLifeCycles(finishedRoot,current,nextEffect,committedExpirationTime,);}if (effectTag & Ref) {recordEffect();commitAttachRef(nextEffect);}if (enableHooks && effectTag & Passive) {rootWithPendingPassiveEffects = finishedRoot;}nextEffect = nextEffect.nextEffect;}
}
  • 进入 commitLifeCycles

    function commitLifeCycles(finishedRoot: FiberRoot,current: Fiber | null,finishedWork: Fiber,committedExpirationTime: ExpirationTime,
    ): void {// 根据不同的组件类型来执行不同的操作switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent: {commitHookEffectList(UnmountLayout, MountLayout, finishedWork);break;}// 主要是 ClassComponent,它会根据 current 是否等于 null 来执行不同的生命周期方法// 一个是 componentDidMount 跟 componentDidUpdate// 因为我们只有在第一次渲染的时候才会调用 componentDidMount 这个生命周期方法// 后续调用的都是 componentDidUpdate 它们的调用方式会有一定的区别// 这里是 ClassComponent 它的一个commit的过程case ClassComponent: {const instance = finishedWork.stateNode;if (finishedWork.effectTag & Update) {// 第一次调用if (current === null) {startPhaseTimer(finishedWork, 'componentDidMount');// We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.if (__DEV__) {if (finishedWork.type === finishedWork.elementType) {warning(instance.props === finishedWork.memoizedProps,'Expected instance props to match memoized props before ' +'componentDidMount. This is likely due to a bug in React. ' +'Please file an issue.',);warning(instance.state === finishedWork.memoizedState,'Expected instance state to match memoized state before ' +'componentDidMount. This is likely due to a bug in React. ' +'Please file an issue.',);}}instance.componentDidMount();stopPhaseTimer();} else {// 更新const prevProps =finishedWork.elementType === finishedWork.type? current.memoizedProps: resolveDefaultProps(finishedWork.type, current.memoizedProps);const prevState = current.memoizedState;startPhaseTimer(finishedWork, 'componentDidUpdate');// We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.if (__DEV__) {if (finishedWork.type === finishedWork.elementType) {warning(instance.props === finishedWork.memoizedProps,'Expected instance props to match memoized props before ' +'componentDidUpdate. This is likely due to a bug in React. ' +'Please file an issue.',);warning(instance.state === finishedWork.memoizedState,'Expected instance state to match memoized state before ' +'componentDidUpdate. This is likely due to a bug in React. ' +'Please file an issue.',);}}instance.componentDidUpdate(prevProps,prevState,instance.__reactInternalSnapshotBeforeUpdate, // 在 commitRoot 的方法里面去生成的 instance 对象上面的 snapshot 快照);stopPhaseTimer();}}const updateQueue = finishedWork.updateQueue;if (updateQueue !== null) {if (__DEV__) {if (finishedWork.type === finishedWork.elementType) {warning(instance.props === finishedWork.memoizedProps,'Expected instance props to match memoized props before ' +'processing the update queue. This is likely due to a bug in React. ' +'Please file an issue.',);warning(instance.state === finishedWork.memoizedState,'Expected instance state to match memoized state before ' +'processing the update queue. This is likely due to a bug in React. ' +'Please file an issue.',);}}// We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.// 对于 ClassComponent,还需要做一件事情,就是去获取它的 updatequeen// 然后 执行 commitUpdateQueue 这么一个方法,我们要拿到他的 propscommitUpdateQueue(finishedWork,updateQueue,instance,committedExpirationTime,);}return;}// 对于 HostRoot,它也会执行 commitUpdateQueue// 因为对于 HostRoot 来说,基本上只有在调用 ReactDOM.render 的时候才会创建update// 它的 commitUpdateQueue, 能做什么呢?对于调用 ReactDOM.render 是可以传3个参数的// 第3个参数就是在第一次 render 结束之后,它的一个回调, 执行过程类似case HostRoot: {const updateQueue = finishedWork.updateQueue;if (updateQueue !== null) {let instance = null;// 注意这里,有子树的场景if (finishedWork.child !== null) {switch (finishedWork.child.tag) {// 这里case HostComponent:instance = getPublicInstance(finishedWork.child.stateNode); // 获取 对应 HostComponent 的 dom 节点,就是返回传参break;// 这里,其实和上面一样case ClassComponent:instance = finishedWork.child.stateNode;break;}}commitUpdateQueue(finishedWork,updateQueue,instance,committedExpirationTime,);}return;}case HostComponent: {const instance: Instance = finishedWork.stateNode;// Renderers may schedule work to be done after host components are mounted// (eg DOM renderer may schedule auto-focus for inputs and form controls).// These effects should only be committed when components are first mounted,// aka when there is no current/alternate.// 参考下面的 commitMount 注释if (current === null && finishedWork.effectTag & Update) {const type = finishedWork.type;const props = finishedWork.memoizedProps;commitMount(instance, type, props, finishedWork);}return;}case HostText: {// We have no life-cycles associated with text.return;}case HostPortal: {// We have no life-cycles associated with portals.return;}case Profiler: {if (enableProfilerTimer) {const onRender = finishedWork.memoizedProps.onRender;if (enableSchedulerTracing) {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),finishedRoot.memoizedInteractions,);} else {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),);}}return;}case SuspenseComponent:break;case IncompleteClassComponent:break;default: {invariant(false,'This unit of work tag should not have side-effects. This error is ' +'likely caused by a bug in React. Please file an issue.',);}}
    }
    
    • 进入 commitUpdateQueue
      // packages/react-reconciler/src/ReactUpdateQueue.js#L571
      export function commitUpdateQueue<State>(finishedWork: Fiber,finishedQueue: UpdateQueue<State>,instance: any,renderExpirationTime: ExpirationTime,
      ): void {// If the finished render included captured updates, and there are still// lower priority updates left over, we need to keep the captured updates// in the queue so that they are rebased and not dropped once we process the// queue again at the lower priority.// 这里做了一个判断,是否有 firstCapturedUpdate,在 finishedqueen 上面, 也就是组件的 updateQueen 上面// 这个 updateQueue上面, 如果有 capturedupdate,就是说我们在渲染这个组件的子树的时候,如果有异常出现// 并且被这个组件捕获,会产生这部分的update, 这部分的update,有可能在这一次的渲染周期里面, 没有被执行完// 其实这部分它有一个用意,就是说对于被捕获的错误,我们如果在这一次渲染周期里面无法去处理// 没有办法去完成它,我们把它放到这个组件上面,如果它还有低优先级的更新的话// 那么我们把它放到低优先级的更新上面去看他是否在那个低优先级的更新上能够被处理// 如果我们没有低优先级的处理了, 如果我们没有低优先级的更新了// 我们在本次渲染当中捕获的更新,就直接给它清空了// 因为我们本次渲染里面没有完成,为了不影响之后产生的更新,我们就直接清空它了// 而不需要去让它在后续影响整个组件的一个更新过程if (finishedQueue.firstCapturedUpdate !== null) {// Join the captured update list to the end of the normal list.// 这个时候它会进行一个判断, 如果我们现在这个组件上面还有 update 不是的 capturedUpdate, 而是普通的我们自己创建的updateif (finishedQueue.lastUpdate !== null) {// 就把capturedupdate放到正常的update的后面,就把它在链表上面去给它接在最后面这一部分finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;}// Clear the list of captured updates.// 不管有没有,我们都要执行,把firstCapturedUpdate到lastCapturedUpdate这个链表上面的数据清空finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;}// Commit the effects// commitUpdateEffects 它不仅仅要对 firstEffect 来执行commitUpdateEffects(finishedQueue.firstEffect, instance);finishedQueue.firstEffect = finishedQueue.lastEffect = null;// 而同样的对于 capturedEffect 也要进行执行// 比如之前 workLoop 中看到过的 throw exception 里面// 去捕获了这个错误,然后为 classcomponent 创建了一个 update// throw exception 里面,在 unwindwork 中的 createClassErrorUpdate, 在里面 放入了update它的callback// 就是去调用 componentDidCatch 这个生命周期方法,这个callback 也是会在这个地方被执行的commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
      }
      
      • 进入 commitUpdateEffects
        function commitUpdateEffects<State>(effect: Update<State> | null,instance: any,
        ): void {while (effect !== null) {const callback = effect.callback;// 判断一下这个 effect 上面是否有 callbackif (callback !== null) {effect.callback = null;// 如果有callback,我们执行callbackcallCallback(callback, instance);}effect = effect.nextEffect;}
        }
        // setState 中的 回调就是在这个时候执行的
        // 在这个时间点上,这个 update 对应的更新已经执行了,它已经反映到了我们的 state 和 props 上面
        // 在 setState 中的 回调才会执行
        // 在之前的 processUpdateQueue 方法里面,没有执行回调的过程的,如果有回调,回调就会把它放到对应的 update 里面
        function callCallback(callback, context) {invariant(typeof callback === 'function','Invalid argument passed as callback. Expected a function. Instead ' +'received: %s',callback,);callback.call(context);
        }
        
    • 进入 commitMount
      // 先判断了一下是否要 autofocus 这个 HostComponent
      // 在之前 completeUnitOfWork 的时候,会判断过这个组件是否有 autofocus 这个属性的
      // 然后会给它加上一个 update 的 SideEffect
      // 而在这里,同样判断它是是否有 update 这个 SideEffect
      // 同时它只有在 current 等于 null 的一个情况下才会被调用 (限制条件在外部调用方)
      // 其实 这也就符合我们对于 autofocus 的一个认知
      // 在页面初次渲染的时候,它才会去自动获取需要被 autofocus 的节点 
      // 同样的对于react当中它的一个判断方式就非常的简单,就是通过current是否等于null来进行判断。
      // 如果有update,我们就执行这个方法来判断一下它是否是一个autofocus的组件。
      // 然后进行一个手动触发这个focus的过程
      export function commitMount(domElement: Instance,type: string,newProps: Props,internalInstanceHandle: Object,
      ): void {// Despite the naming that might imply otherwise, this method only// fires if there is an `Update` effect scheduled during mounting.// This happens if `finalizeInitialChildren` returns `true` (which it// does to implement the `autoFocus` attribute on the client). But// there are also other cases when this might happen (such as patching// up text content during hydration mismatch). So we'll check this again.if (shouldAutoFocusHostComponent(type, newProps)) {((domElement: any):| HTMLButtonElement| HTMLInputElement| HTMLSelectElement| HTMLTextAreaElement).focus();}
      }function shouldAutoFocusHostComponent(type: string, props: Props): boolean {switch (type) {case 'button':case 'input':case 'select':case 'textarea':return !!props.autoFocus;}return false;
      }
      
  • 以上是对所有的具有 SideEffect 的节点在commit过程当中执行任务的一个过程,到这里,整个dom树其实也已经渲染上去了

  • commitAllLifeCycles 其实主要是调用不同组件的一个生命周期,以及对于 HostComponent,等组件的不同处理

  • 包括可能存在 autofocus 的处理过程,所以它相当于是在整个应用渲染完成之后的一个善后工作

  • 具体细节,写在上述代码的注释中

相关文章:

  • HTML-框架标签、实体、全局属性和元信息
  • 编曲学习:和声音程 调式体系 唱名法 调式调性
  • Java和Redis实现一个简单的热搜功能
  • YOLOv8融合改进 更换检测头同时改进C2f模块
  • 详讲api网关之kong的基本概念及安装和使用(一)
  • web安全学习笔记【08】——算法1
  • Qt/QML编程之路:ListView实现横排图片列表的示例(40)
  • React中文官网已经搬迁了,原网址内容将不再更新
  • std::for_each
  • 【小呆的力学笔记】弹塑性力学的初步认知二:应力应变分析(2)
  • 小黑艰难的前端啃bug之路:内联元素之间的间隙问题
  • Python 进阶语法:lambda函数
  • 浅聊 DNS 和 host
  • MySQL索引类型及数据结构【笔记】
  • obsidian阅读pdf和文献——与zotero连用
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 4. 路由到控制器 - Laravel从零开始教程
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • emacs初体验
  • go语言学习初探(一)
  • JavaScript异步流程控制的前世今生
  • Java基本数据类型之Number
  • JS笔记四:作用域、变量(函数)提升
  • js学习笔记
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • node入门
  • windows下使用nginx调试简介
  • yii2权限控制rbac之rule详细讲解
  • 给Prometheus造假数据的方法
  • 免费小说阅读小程序
  • 区块链技术特点之去中心化特性
  • 人脸识别最新开发经验demo
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 使用putty远程连接linux
  • 学习笔记:对象,原型和继承(1)
  • 延迟脚本的方式
  • 云大使推广中的常见热门问题
  • const的用法,特别是用在函数前面与后面的区别
  • 阿里云服务器如何修改远程端口?
  • 说说我为什么看好Spring Cloud Alibaba
  • !!Dom4j 学习笔记
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (3)STL算法之搜索
  • (6)添加vue-cookie
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (顺序)容器的好伴侣 --- 容器适配器