|
|
|
>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 逻辑结束
|