关于源码分析:minireact新版本fiber架构

7次阅读

共计 2981 个字符,预计需要花费 8 分钟才能阅读完成。

之前写了一篇 stack 版的 mini-react 实现, 这里再写一篇 fiber 版的实现。

这里如果不晓得两者的区别的话, 举荐先看看我这一篇文章:
stack 和 fiber 架构的区别

从我下面连贯这篇文章咱们能够晓得:React 16 之前的版本比对更新 VirtualDOM 的过程是采纳循环加递归实现的,这种比对形式有一个问题,就是一旦工作开始进行就无奈中断,如果利用中组件数量宏大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新实现之后主线程能力被开释,主线程能力执行其余工作。这就会导致一些用户交互,动画等工作无奈立刻失去执行,页面就会产生卡顿, 十分的影响用户体验。

其次要问题是:递归无奈中断,执行重工作耗时长。JavaScript 又是单线程,无奈同时执行其余工作,导致工作提早页面卡顿,用户体验差。

咱们得解决方案是:

  1. 利用浏览器闲暇工夫执行工作,回绝长时间占用主线程
  2. 放弃递归只采纳循环,因为循环能够被中断
  3. 工作拆分,将工作拆分成一个个的小工作

基于以上几点,在这里咱们先理解下 requestIdleCallback 这个 api

外围 API 性能介绍:利用浏览器的空余工夫执行工作,如果有更高优先级的工作要执行时,以后执行的工作能够被终止,优先执行高级别工作。

requestIdleCallback(function(deadline) {// deadline.timeRemaining() 获取浏览器的空余工夫
})

这里咱们理解下什么是浏览器空余工夫:页面是一帧一帧绘制进去的,当每秒绘制的帧数达到 60 时,页面是晦涩的,小于这个值时,用户会感觉到卡顿,1s 60 帧,每一帧分到的工夫是 1000/60 ≈ 16 ms,如果每一帧执行的工夫小于 16ms,就阐明浏览器有空余工夫。

如果工作在残余的工夫内没有实现则会进行工作执行,持续优先执行主工作,也就是说 requestIdleCallback 总是利用浏览器的空余工夫执行工作。

咱们先用这个 api 做个例子,来看:
html

<div class="playground" id="play">playground</div>
<button id="work">start work</button>
<button id="interaction">handle some user interaction</button>

css

<style>
  .playground {
    background: palevioletred;
    padding: 20px;
    margin-bottom: 10px;
  }
</style>

js

var play = document.getElementById("play")
var workBtn = document.getElementById("work")
var interactionBtn = document.getElementById("interaction")
var iterationCount = 100000000
var value = 0

var expensiveCalculation = function (IdleDeadline) {while (iterationCount > 0 && IdleDeadline.timeRemaining() > 1) {
    value =
      Math.random() < 0.5 ? value + Math.random() : value + Math.random()
    iterationCount = iterationCount - 1
  }
  requestIdleCallback(expensiveCalculation)
}

workBtn.addEventListener("click", function () {requestIdleCallback(expensiveCalculation)
})

interactionBtn.addEventListener("click", function () {play.style.background = "palegreen"})

从这个示例中咱们晓得了,这个 api 该如何应用,该如何中断工作。

而后咱们来实现咱们得 fiber 架构得 react-mini 版本。

在 Fiber 计划中,为了实现工作的终止再持续,DOM 比对算法被分成了两局部:

  1. 构建 Fiber (可中断)
  2. 提交 Commit (不可中断, 更新 dom)

目前咱们设计得 fiber 对象有以下属性:

{type         节点类型 ( 元素, 文本, 组件)(具体的类型)
  props        节点属性
  stateNode    节点 DOM 对象 | 组件实例对象
  tag          节点标记 (对具体类型的分类 hostRoot || hostComponent || classComponent || functionComponent)
  effects      数组, 存储须要更改的 fiber 对象
  effectTag    以后 Fiber 要被执行的操作 (新增, 删除, 批改)
  parent       以后 Fiber 的父级 Fiber
  child        以后 Fiber 的子级 Fiber
  sibling      以后 Fiber 的下一个兄弟 Fiber
  alternate    Fiber 备份 fiber 比对时应用
}

这时咱们的我的项目构造

react/index.js 是入口文件, 在这里咱们对它做 react 次要应用 api 的导出

import createElement from "./CreateElement"
export {render} from "./reconciliation"
export {Component} from "./Component"

export default {createElement}

而后咱们先来写 createElement 用来把 jsx 生成 vnode 的函数:
react/CreateElement

export default function createElement(type, props, ...children) {const childElements = [].concat(...children).reduce((result, child) => {if (child !== false && child !== true && child !== null) {if (child instanceof Object) {result.push(child)
      } else {result.push(createElement("text", { textContent: child}))
      }
    }
    return result
  }, [])
  return {
    type,
    props: Object.assign({children: childElements}, props)
  }
}

这个的实现是和 stack 架构一样的

接下来就是咱们的 render 函数,reconciliation/index.js 这里也就是咱们外围的协调算法了.

在这里咱们次要工作是分两个阶段:
1. 依据 vnode 来生成 fiber 对象, 生成 fiber 的过程是通过循环来生成的, 因为咱们生成 fiber 以及它的子集 fiber 的过程是能够被打断的, 所以咱们通过循环的形式来生成, 也就是说咱们当初的 fiber 的构造是一个链表的构造.

这是一个树的构造, 在咱们生成的 fiber 对象中, 会有 parent 指向父节点,child 指向咱们以后子集的第一个的节点,也就是最左侧的节点, 子集中其余节点是咱们这个 child 的兄弟节点 sibling。

正文完
 0