>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` `push` 进 `style` 和 `styleUpdates` - 否则给 `styleUpdates` 赋值 `props style` 属性 - 当找到特殊属性 `dangerouslySetInnerHTML` : - 会与旧 props 的 `dangerouslySetInnerHTML` 对比,发生变化则 push 进 updatePayload 数组 - 当找到 children 属性,会将其转换为字符串和 属性名一起 `push` 进 `updatePayload` - 当找到除 `dangerouslySetInnerHTML` 之外的特殊属性,会对其进行专属的逻辑 - 最后没有找到以上属性,会进入最后的 else 逻辑:给 `updatePayload` 数组 `push` 进 props 属性名和 对应的属性值,进入下一轮循环 当所有的循环结束会判断 styleUpdates 是否合法(不为 null,且数组长度不为0),合法则进入将调用 validateShorthandPropertyCollisionInDev 函数,传递 styleUpdates 和 新 props style 属性,最后将 `style 字符串` 和 `styleUpdates` 推入 `updatePayload` 返回 `updatePayload` 回到 `updateHostComponent` 函数,将 `prepareUpdate` 也就是 `diffProperties` 返回的 `updatePayload` 赋值给 `workInProgress` 的 `updateQueue` 属性,如果它不为空则执行 `markUpdate` 函数 ### markUpdate 为传入的 workInProgress 打上 Update 标记 返回 null,回到 `completeWork` 的调用栈,如果 current ref 和 workInProgress ref 不同,调用 markRef 函数 ### markRef 为传入的 workInProgress 打上 Ref 和 RefStatic 标记 最后调用 bubbleProperties 函数,这又是一个比较长的函数,~~暂且不谈~~ completeWork 逻辑结束