这里若是不知道二者的区别的话,推荐先看看我这一篇文章:
stack和fiber架构的区别css
从我上面链接这篇文章咱们能够知道:React 16 以前的版本比对更新 VirtualDOM 的过程是采用循环加递归实现的,这种比对方式有一个问题,就是一旦任务开始进行就没法中断,若是应用中组件数量庞大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新完成以后主线程才能被释放,主线程才能执行其余任务。这就会致使一些用户交互,动画等任务没法当即获得执行,页面就会产生卡顿, 很是的影响用户体验。 html
其主要问题是:递归没法中断,执行重任务耗时长。 JavaScript 又是单线程,没法同时执行其余任务,致使任务延迟页面卡顿,用户体验差。node
咱们得解决方案是:react
基于以上几点,在这里咱们先了解下requestIdleCallback这个api算法
核心 API 功能介绍:利用浏览器的空余时间执行任务,若是有更高优先级的任务要执行时,当前执行的任务能够被终止,优先执行高级别任务。segmentfault
requestIdleCallback(function(deadline) { // deadline.timeRemaining() 获取浏览器的空余时间 })
这里咱们了解下什么是浏览器空余时间:页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时, 用户会感受到卡顿,1s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,若是每一帧执行的时间小于16ms,就说明浏览器有空余时间。api
若是任务在剩余的时间内没有完成则会中止任务执行,继续优先执行主任务,也就是说 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比对算法被分红了两部分:
目前咱们设计得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。
每次构建一个子集就是一个子任务,fiber任务是能够被打断的。
2.咱们每次构建fiber会把当前fiber以及自身收集到的子集fiber的effects数组放入父级的effects数组中,而后第二个阶段是commit生成dom阶段,咱们只须要循环最外层的fiber中effects数组就能够,这个数组中放着全部的fiber对象,咱们只须要依次把它们的stateNode 也就是dom对象根据effectTag操做符,来对比更新,删除新增到它的parent父级的stateNode中.
这样咱们一个完整流程就完成了.