1
0
Fork 0
Obsidian 管理的个人笔记仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

7.6 KiB

completeWork 执行在递归节点的 Fiber 创建之后,主要是为创建好的 Fiber 节点插入内容和插入真实 DOM 树

函数开始,会执行 popTreeContext 并传入 workInProgress,这个函数应该也和 Context 相关,然后进入到 swtich 逻辑,根据 WorkInProgress Fiber 节点的 tag 属性进入不同的 case 逻辑,这里和 beginWork 基本上类似 React 的遍历顺序是从父到子,最终再从子回到父,所以首屏渲染中首先进入 completeWork 的 WorkInProgress 不一定会是 FiberNode ,在这里是 HostComponent 进入到 HostComponent 的 case 之后,又执行了一遍 popTreeContext,官方的注释也写明似乎是有一些考虑在 在 HostComponent 的 case 中,会判断 current 和 WorkInProgress.stateNode 是否为空,在首屏渲染的 completeWork 节点,这里都是空的,所以进入为空的逻辑:先检查当前 Fiber 节点是否存在 props 属性,最后调用 createInstance 创建一个 DOM 实例 createInstance 会调用 createElement 方法创建一个 DOM 实例,并调用 precacheFiberNode 和 updateFiberProps 方法,这两个方法都是在插入属性,一个在当前 WorkInProgress Fiber 节点上插入 DOM,一个是在 DOM 上插入 props,最后将处理好的 DOM 返回

appendAllChildren

函数首先会判断 WorkInProgress 的 child 属性是否为空,如果不为空就进入 while 循环,因为这里是首屏渲染的第一次 completeWork 所以进入到一定是最小的子组件,不会存在 child 属性,所以会跳过循环,也就会跳过插入逻辑,回到 completeWork 的调用栈中

然后回到 completeWork 的调用栈,它会将处理完毕的 DOM 示例插入到 WorkInProgress 的 stateNode 属性中,然后会调用 inalizeInitialChildren ,这个函数会为创建的 DOM 元素,插入已有的 props,内部也根据 Fiber 节点的 tag 区分不同的处理逻辑,还有对 props 是否合法的校验,甚至根据 props 的属性也做了不同逻辑的处理,最后会返回一个 Boolean 用于判断是否进入 markUpdate 逻辑,这个函数会把 Fiber 打上 Update 标记 最后再执行 bubbleProperties 进行一些处理,不谈 整个 completeWork 的流程就走完了

completeWork

和首屏渲染不同,这次 current 和 WorkInProgress.stateNode 不为空,所以会进入和首屏渲染时不一样的逻辑:调用 updateHostComponent 函数

updateHostComponent

函数内部会对当前 WorkInProgress Fiber 节点的新旧 props 进行对比,如果完全相同会直接返回,这里的完全相同实际上是指引用地址也相同,所以在本次就算新旧 props 相同也并会被 return,然后取出 Fiber 中的 stateNode 传递给 prepareUpdate 函数,然后被调用 diffProperties 函数,这里还有一个针对 props 的 children 属性是否为字符串或者数字的判断,暂时还不清楚具体目的是什么

diffProperties

函数开始会先执行对 props 属性的校验方法:validatePropertiesInDevelopment 方法,然后根据 Fiber tag 进入不同的 case,这里只有针对三种 tag有特殊的处理,分别是:input、select 和 textarea,本次进入 completeWork 的 Fiber 节点 tag 为 img,所以不会进入 特殊处理的 case,最终进入到 default 逻辑,然后被直接 break,去往 assertValidProps 函数 这里还有一个针对 Fiber 旧 props 属性 onClick 不是 function 且 新 props 属性 onClick 是 function 的一个特殊处理:就是直接给 DOM 的 onclick 属性赋值为一个空函数 ,从官方留下的注释中大概可以知道,这似乎是为了修复 Safari 浏览器的问题的特殊处理

assertValidProps

函数一开始会检查 props 是否存在,不存在直接 return 然后就是一大串的 if 用于对 props 是否合法的兜底操作,比如说 dangerouslySetInnerHTML 属性和 children 属性智能存在一个之类的,如果找到不合法的操作会直接通过 throw Error 被抛出

之后就进入 diffProperties 最主要的任务:对新旧 props 进行对比然后生成 updateQueue 这里有两个 for in 循环:循环旧 props 属性和循环新 props 属性

警告:以下解析受限于我的表达能力,可能会有读起来一头雾水的情况,建议和源码一起看

循环旧 props 属性:

  • 以下条件跳出当前循环:
    1. 新 props 自身不存在相同属性
    2. 属性非旧 props 自身属性
    3. 该属性在旧 props 的值为 null
  • 当找到 style 属性,会遍历 style 属性,初始化 styleUpdates 对象为空,并在上边新增当前 style 属性的 key 并赋值空字符串
  • 当找到一些特殊属性如 dangerouslySetInnerHTML 等,会初始化 updatePayload 为空数组
  • 如果没有以上属性,即没有 style 和 特殊属性,会进入最后的 else 逻辑:给 updatePayload 数组 push 进属性名和 null,进入下一轮循环

循环新 props 属性:

  • 以下条件跳出当前循环:
    1. 新 props 自身不存在当前属性
    2. 新 props 属性值和旧 props 属性值相同(引用相同)
    3. 新旧 props 属性值都为 null
  • 当找到 style 属性:
    • 如果新 props 属性值存在且值合法(就是转换成 boolean 不为 false),会调用 Object.freeze 函数冻结当前属性值
    • 如果旧 props 也存在 style 属性且不等于 null:
      • 开始遍历旧 props style
        • 如果出现旧 props style 有 style 样式,但是新 props style 没有的也就是新增样式的情况,那么会初始化 styleUpdates 对象为空(如果已经初始化过就会跳过),并在上边新增当前 style 属性的 key 并赋值空字符串
      • 开始遍历新 props style
        • 如果找到新 props style 属性存在,但是与旧 props style 属性不同,也就是样式发生修改的情况,那么会初始化 styleUpdates 对象为空(如果已经初始化过就会跳过),并在上边对当前 style 属性的 key 做新增或是修改,值为新 props style 属性
    • 如果旧 props 不存在 style 属性 或者等于 null
      • 如果 styleUpdates 不为 null,且是数组时长度不等于 0,给 updatePayload pushstylestyleUpdates
      • 否则给 styleUpdates 赋值 props style 属性
  • 当找到特殊属性 dangerouslySetInnerHTML
    • 会与旧 props 的 dangerouslySetInnerHTML 对比,发生变化则 push 进 updatePayload 数组
  • 当找到 children 属性,会将其转换为字符串和 属性名一起 pushupdatePayload
  • 当找到除 dangerouslySetInnerHTML 之外的特殊属性,会对其进行专属的逻辑
  • 最后没有找到以上属性,会进入最后的 else 逻辑:给 updatePayload 数组 push 进 props 属性名和 对应的属性值,进入下一轮循环

当所有的循环结束会判断 styleUpdates 是否合法(不为 null,且数组长度不为0),合法则进入将调用 validateShorthandPropertyCollisionInDev 函数,传递 styleUpdates 和 新 props style 属性,最后将 style 字符串styleUpdates 推入 updatePayload 返回 updatePayload

回到 updateHostComponent 函数,将 prepareUpdate 也就是 diffProperties 返回的 updatePayload 赋值给 workInProgressupdateQueue 属性,如果它不为空则执行 markUpdate 函数

markUpdate

为传入的 workInProgress 打上 Update 标记

返回 null,回到 completeWork 的调用栈,如果 current ref 和 workInProgress ref 不同,调用 markRef 函数

markRef

为传入的 workInProgress 打上 Ref 和 RefStatic 标记

最后调用 bubbleProperties 函数,这又是一个比较长的函数,暂且不谈

completeWork 逻辑结束