React Fiber

React Fiber是对核心算法的一次重新实现

React Fiber的方式 破解JavaScript中同步操作时间过长的方法其实很简单——分片。

把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。

React
Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。

维护每一个分片的数据结构,就是Fiber。

源码地址: https://github.com/qifutian/learngit/tree/main/Fiber

1. 开发环境配置

React Fiber
React Fiber
1.3 环境配置
定义server.js,创建web服务器

import express from "express"

const app = express()

app.use(express.static("dist"))

const template = `
  <html>
    <head>
      <title>React Fiber</title>
    </head>
    <body>
      <div id="root"></div>
      <script src="bundle.js"></script>
    </body>
  </html>
`

app.get("*", (req, res) => {
  res.send(template)
})

app.listen(3000, () => console.log("server is running"))

webpack.config.serve.js 配置服务端打包

const path = require("path")
const nodeExternals = require("webpack-node-externals")

module.exports = {
  target: "node",
  mode: "development",
  entry: "./server.js",
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "server.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  externals: [nodeExternals()]
}

修改babel.config.json

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

修改package.json
针对serve.js进行打包
运行命令,执行build下的server.js打包
start运行进行统一运行

{
  "name": "fiber",
  "version": "1.0.0",
  "main": "babel.config.js",
  "scripts": {
    "start": "npm-run-all --parallel dev:*",
    "dev:server-compile": "webpack --config webpack.config.server.js --watch",
    "dev:server": "nodemon ./build/server.js",
    "dev:client-compile": "webpack --config webpack.config.client.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "@babel/preset-react": "^7.10.4",
    "babel-loader": "^8.1.0",
    "nodemon": "^2.0.4",
    "npm-run-all": "^4.1.5",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-node-externals": "^2.5.2"
  },
  "dependencies": {
    "express": "^4.17.1"
  },
  "description": ""
}

客户端打包配置 wenpack.config.client.js

const path = require("path")

module.exports = {
  target: "web",
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js"
  },
  devtool: "source-map",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
}

requestIdleCallback

核心API 功能介绍

利用浏览器的空闲时间执行任务,如果有更高优先级的任务要执行时,当前执行的任务可以被终止,优先执行更高级别任务

应用场景:例如有一个计算任务要执行,计算任务需要花费比较长时间执行,在执行任务时候,浏览器会被一直占用,不能执行其他任务,浏览器不能响应,体验不好。可以利用requestIdleCallback注册事件,利用空余时间执行,高任务先执行,执行完之后,利用空余时间执行

是window下一个方法,可以直接调用
requestIdleCallback(function(deadline) {
  // deadline.timeRemaining() 获取浏览器的空余时间,根据事件多少,进行是不是执行该任务
})

浏览器空余时间说明

页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时, 用户会感觉到卡顿

1s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,如果每一帧执行的时间小于16ms,就说明浏览器有空余时间

如果任务在剩余的时间内没有完成则会停止任务执行,继续优先执行主任务,也就是说 requestIdleCallback 总是利用浏览器的空余时间执行任务

API 功能体验

页面中有两个按钮和一个DIV,点击第一个按钮执行一项昂贵的计算,使其长期占用主线程,当计算任务执行的时候去点击第二个按钮更改页面中 DIV 的背景颜色。

使用 requestIdleCallback 就可以完美解决这个卡顿问题。

单纯执行计算任务之后再点击修改颜色会很卡顿

<div class="playground" id="play">playground</div>
<button id="work">start work</button>
<button id="interaction">handle some user interaction</button>
<style>
  .playground {
    background: palevioletred;
    padding: 20px;
    margin-bottom: 10px;
  }
</style>
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"
})

Fiber

在react 16 中,官方对于内部代码做了大量的重写,其中 Fiber就是重要的一部分。
什么是 React Fiber ?

  • 就是一种react比对virtual dom的一种新的算法,fiber是算法名字
  • 之前的对比方式是采用循环加递归的方式实现的 ,有一个问题,就是递归需要层层进入,不能中断,组件特别多,主线程被占用,会产生卡顿,执行时间耗时长

解决方式

  1. 利用浏览器空闲时间执行任务,拒绝长时间占用主线程
  2. 放弃递归只采用循环,因为循环可以被中断
  3. 任务拆分,任务的单元必须要小,将任务拆分成一个个的小任务,重新执行的耗费小很多

实现思路

Fiber是具体实现

在 Fiber 方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:

  1. 构建 Fiber (可中断) virtual dom对象的比对
  2. 提交 Commit (不可中断) 真实dom的更新

DOM 初始渲染: virtualDOM -> Fiber -> Fiber[] -> DOM

DOM 更新操作: newFiber vs oldFiber -> Fiber[] -> DOM

具体过程

  • 使用react时候,依然使用jsx
  • babel将jsx语法转换为react.createElement方法的调用
  • 调用后,但会virtual dom对象
  • 接下来执行第一个阶段,一阶段就是构建Fiber对象,采用循环方式,在这个virtual dom对象中找到每一个内部的vitual
    dom,为每一个virtaul dom对象构建Fiber对象
  • Fiber中存储了很多节点的信息,很重要的一个就是当前节点执行的操作,是删除,更新,会存放在一个数组当中
  • 接下来就是第二阶段操作,循环Fiber数组,循环过程中,根据Fiber对象中存放的目前节点的操作类型,将这个操作应用在真实节点上,这就是执行的大概流程

Fiber 对象

// 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 比对时使用
}

创建任务队列并添加任务

准备一段jsx代码,查看Fiber如何将jsx转化

  1. 首先在src下index.js,准备了jsx代码,准备将代码显示在浏览器中
  2. 将jsx准换成createElement来调用
  3. 在react文件夹下创建index.js ,导出createElement方法,在src下的index.js直接调用
  4. react下创建reconciliation文件夹,放置fiber算法的核心逻辑,index.js有render方法,render方法接收element和dom,从上向下查找,找到每一个dom去创建fiber对象,利用taskQueue.push
  5. Misc放置杂项文件夹,createTaskQueue创建任务队列,pop中使用shilf方法是因为先进先出

index.js

import React,{render} from './react' // 自己实现的简易react对象
const root = document.getElementById("root")
const jsx = (
  <div>
    <p>Hello React</p>
    <p>Hi Fiber</p>
  </div>
)

console.log(jsx)

src/react/index.js

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

export default {
  createElement
}

创建src/react/reconciliation/index.js

import {
  createTaskQueue,  // 导入 创建任务队列
} from "../Misc"
export const render  = (element,dom) => {
  // 1. 向任务队列中添加任务
  // 2. 在浏览器空闲时候执行任务

  // 任务就是通过vdom 对象构建fiber 对象
  // 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列
  
   taskQueue.push({
      dom,  // dom节点
      props: { children: element } // 属性 
   }) // 向任务队列添加任务,任务其实就是js对象
}

src/react/Misc/CreateTaskQueue/index.js

const createTaskQueue = () => {
  const taskQueue = [] 
  return {
    /**
     * 向任务队列中添加任务
     */
    push: item => taskQueue.push(item),  // item 代表任务
    /**
     * 从任务队列中获取任务
     */
    pop: () => taskQueue.shift(), // 从数组前面将任务取出
    /**
     * 判断任务队列中是否还有任务
     */
    isEmpty: () => taskQueue.length === 0
  }
}

export default createTaskQueue

src/react/Misc/index.js

// 从CreateTaskQueue文件夹下index.js引入,再导出createTaskQueue这个方法
export { default as createTaskQueue } from "./CreateTaskQueue"

实现任务队列调度逻辑

  1. 在任务调度中,调用render方法,向任务队列中添加任务,在浏览器空闲时间执行任务
  2. 在performTask中,调用workLoop方法执行任务,判断任务实参还有,是否执行
  3. workLoop专门执行任务,如果子任务不存在去获取子任务,如果存在且浏览器存在空闲时间,调用executeTask接收任务,返回新任务

src/react/reconciliation/index.js

import {
  createTaskQueue,  // 导入 创建任务队列
} from "../Misc"

const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () = > {

}
const executeTask = fiber = > {

}
const workLoop = deadline =>{
  // 调用任务需要先判断是否存在任务
  if(!subTask){
      subTask = getFirstTask()  // 没有任务调用获取
  }
   /**
   * 如果任务存在并且浏览器有空余时间就调用
   * executeTask 方法执行任务 接受任务 返回新的任务
   */
  while (subTask && deadline.timeRemaining() > 1) {
    subTask = executeTask(subTask)
  }

  if (pendingCommit) { // 任务更高级,打断当前任务,将任务保存起来
    commitAllWork(pendingCommit)
  }
}
const performTask = deadline = => {  // 当前方法不负责执行任务,只负责调度任务
  // 获取浏览器的空闲时间
  workLoop(deadline)
  /**
   * 判断任务是否存在
   * 判断任务队列中是否还有任务没有执行
   * 再一次告诉浏览器在空闲的时间执行任务
   */
  if (subTask || !taskQueue.isEmpty()) {
    requestIdleCallback(performTask)
  }
}
export const render  = (element,dom) => {
  // 1. 向任务队列中添加任务
  // 2. 在浏览器空闲时候执行任务

  // 任务就是通过vdom 对象构建fiber 对象
  // 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列
  
   taskQueue.push({
      dom,  // dom节点
      props: { children: element } // 属性 
   }) // 向任务队列添加任务,任务其实就是js对象
   requestIdleCallback(performTask)
}

构建根节点的Fiber对象

当前执行的任务就是为每一个节点构建Fiber对象
就是创建 1 的节点,任何创建2,3,并且构建他们之间的关系
需要注意,只有第一个子集才是父集的子集,第二个子集是第一个子集的下一个兄弟节点
React Fiber
src/react/reconciliation/index.js
getFirstTask中拿到task对象,就需要在while中调用循环,就是最外层根节点传递给 executeTask

import {
  createTaskQueue,  // 导入 创建任务队列
} from "../Misc"

const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () => {
  /**
   * 从任务队列中获取任务
   */
  const task = taskQueue.pop()

  if (task.from === "class_component") {
    const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: "host_root",
      effects: [],
      child: null,
      alternate: root
    }
  }

  /**
   * 返回最外层节点的fiber对象
   */
  return {
    props: task.props,
    stateNode: task.dom,
    tag: "host_root",
    effects: [],
    child: null,
    alternate: task.dom.__rootFiberContainer
  }
}
const executeTask = fiber = > {

}
const workLoop = deadline =>{
  // 调用任务需要先判断是否存在任务
  if(!subTask){
      subTask = getFirstTask()  // 没有任务调用获取
  }
   /**
   * 如果任务存在并且浏览器有空余时间就调用
   * executeTask 方法执行任务 接受任务 返回新的任务
   */
  while (subTask && deadline.timeRemaining() > 1) {
    subTask = executeTask(subTask)
  }

  if (pendingCommit) { // 任务更高级,打断当前任务,将任务保存起来
    commitAllWork(pendingCommit)
  }
}
const performTask = deadline = => {  // 当前方法不负责执行任务,只负责调度任务
  // 获取浏览器的空闲时间
  workLoop(deadline)
  /**
   * 判断任务是否存在
   * 判断任务队列中是否还有任务没有执行
   * 再一次告诉浏览器在空闲的时间执行任务
   */
  if (subTask || !taskQueue.isEmpty()) {
    requestIdleCallback(performTask)
  }
}
export const render  = (element,dom) => {
  // 1. 向任务队列中添加任务
  // 2. 在浏览器空闲时候执行任务

  // 任务就是通过vdom 对象构建fiber 对象
  // 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列
  
   taskQueue.push({
      dom,  // dom节点
      props: { children: element } // 属性 
   }) // 向任务队列添加任务,任务其实就是js对象
   requestIdleCallback(performTask)
}

构建子节点的Fiber对象

executeTask方法接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
子节点通过 fiber.props.children来获取

src/react/reconciliation/index.js

import {
  createTaskQueue,  // 导入 创建任务队列
} from "../Misc"

const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () => {
  /**
   * 从任务队列中获取任务
   */
  const task = taskQueue.pop()

  if (task.from === "class_component") {
    const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: "host_root",
      effects: [],
      child: null,
      alternate: root
    }
  }

  /**
   * 返回最外层节点的fiber对象
   */
  return {
    props: task.props,
    stateNode: task.dom,
    tag: "host_root",
    effects: [],
    child: null,
    alternate: task.dom.__rootFiberContainer
  }
}

const reconcileChildren = (fiber, children) => {
  /**
   * children 可能对象 也可能是数组
   * 将children 转换成数组
   */
  const arrifiedChildren = arrified(children)
  /**
   * 循环 children 使用的索引
   */
  let index = 0
  /**
   * children 数组中元素的个数
   */
  let numberOfElements = arrifiedChildren.length
  /**
   * 循环过程中的循环项 就是子节点的 virtualDOM 对象
   */
  let element = null
  /**
   * 子级 fiber 对象
   */
  let newFiber = null
  /**
   * 上一个兄弟 fiber 对象
   */
  let prevFiber = null

  let alternate = null

  if (fiber.alternate && fiber.alternate.child) {
    alternate = fiber.alternate.child
  }

  while (index < numberOfElements || alternate) {
    /**
     * 子级 virtualDOM 对象
     */
    element = arrifiedChildren[index]

    if (!element && alternate) {
      /**
       * 删除操作
       */
      alternate.effectTag = "delete"
      fiber.effects.push(alternate)
    } else if (element && alternate) {
      /**
       * 更新
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: "update",
        parent: fiber,
        alternate
      }
      if (element.type === alternate.type) {
        /**
         * 类型相同
         */
        newFiber.stateNode = alternate.stateNode
      } else {
        /**
         * 类型不同
         */
        newFiber.stateNode = createStateNode(newFiber)
      }
    } else if (element && !alternate) {
      /**
       * 初始渲染
       */
      /**
       * 子级 fiber 对象
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: "placement",
        parent: fiber
      }
      /**
       * 为fiber节点添加DOM对象或组件实例对象
       */
      newFiber.stateNode = createStateNode(newFiber)
    }

    if (index === 0) {
      fiber.child = newFiber
    } else if (element) {
      prevFiber.sibling = newFiber
    }

    if (alternate && alternate.sibling) {
      alternate = alternate.sibling
    } else {
      alternate = null
    }

    // 更新
    prevFiber = newFiber
    index++
  }
}
// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
  /**
   * 构建子级fiber对象
   */
  if (fiber.tag === "class_component") {
    if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
      fiber.stateNode.state = {
        ...fiber.stateNode.state,
        ...fiber.stateNode.__fiber.partialState
      }
    }

    reconcileChildren(fiber, fiber.stateNode.render())
  } else if (fiber.tag === "function_component") {
    reconcileChildren(fiber, fiber.stateNode(fiber.props))
  } else {
    reconcileChildren(fiber, fiber.props.children)
  }
  /**
   * 如果子级存在 返回子级
   * 将这个子级当做父级 构建这个父级下的子级
   */
  if (fiber.child) {
    return fiber.child
  }

  /**
   * 如果存在同级 返回同级 构建同级的子级
   * 如果同级不存在 返回到父级 看父级是否有同级
   */
  let currentExecutelyFiber = fiber

  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
      currentExecutelyFiber.effects.concat([currentExecutelyFiber])
    )
    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling
    }
    currentExecutelyFiber = currentExecutelyFiber.parent
  }
  pendingCommit = currentExecutelyFiber
}
const workLoop = deadline =>{
  // 调用任务需要先判断是否存在任务
  if(!subTask){
      subTask = getFirstTask()  // 没有任务调用获取
  }
   /**
   * 如果任务存在并且浏览器有空余时间就调用
   * executeTask 方法执行任务 接受任务 返回新的任务
   */
  while (subTask && deadline.timeRemaining() > 1) {
    subTask = executeTask(subTask)
  }

  if (pendingCommit) { // 任务更高级,打断当前任务,将任务保存起来
    commitAllWork(pendingCommit)
  }
}
const performTask = deadline = => {  // 当前方法不负责执行任务,只负责调度任务
  // 获取浏览器的空闲时间
  workLoop(deadline)
  /**
   * 判断任务是否存在
   * 判断任务队列中是否还有任务没有执行
   * 再一次告诉浏览器在空闲的时间执行任务
   */
  if (subTask || !taskQueue.isEmpty()) {
    requestIdleCallback(performTask)
  }
}
export const render  = (element,dom) => {
  // 1. 向任务队列中添加任务
  // 2. 在浏览器空闲时候执行任务

  // 任务就是通过vdom 对象构建fiber 对象
  // 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列
  
   taskQueue.push({
      dom,  // dom节点
      props: { children: element } // 属性 
   }) // 向任务队列添加任务,任务其实就是js对象
   requestIdleCallback(performTask)
}

在杂项Misc中新建createStateNode/index.js
创建Fiber中的stateNode节点

import { createDOMElement } from "../../DOM"
import { createReactInstance } from "../createReactInstance"

const createStateNode = fiber => {
  if (fiber.tag === "host_component") {
    return createDOMElement(fiber)
  } else {
    return createReactInstance(fiber)
  }
}

export default createStateNode

创建react下DOM下的index.js,导出对应的createDOMElement和updateDOMElement

export { default as createDOMElement } from "./createDOMElement"
export { default as updateNodeElement } from "./updateNodeElement"

Fiber对象的tag属性
host_component代表就是一个普通节点,处理子节点时候需要是动态的,是什么类型返回什么标记
Misc/getTag/index.js 处理tag对应标签

import { Component } from "../../Component"

const getTag = vdom => {
   // 普通节点是字符串形式
  if (typeof vdom.type === "string") {
    return "host_component"
  } else if (Object.getPrototypeOf(vdom.type) === Component) {
    return "class_component"
  } else {
    return "function_component"
  }
}
export default getTag

Fiber对象的effects数组

effects数组存储所有的fiber对象,在渲染的第二阶段循环该数组,统一获取fiber对象,构建真实dom对象,并且将真实的渲染到页面

// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
  /**
   * 构建子级fiber对象
   */
  if (fiber.tag === "class_component") {
    if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
      fiber.stateNode.state = {
        ...fiber.stateNode.state,
        ...fiber.stateNode.__fiber.partialState
      }
    }

    reconcileChildren(fiber, fiber.stateNode.render())
  } else if (fiber.tag === "function_component") {
    reconcileChildren(fiber, fiber.stateNode(fiber.props))
  } else {
    reconcileChildren(fiber, fiber.props.children)
  }
  /**
   * 如果子级存在 返回子级
   * 将这个子级当做父级 构建这个父级下的子级
   */
  if (fiber.child) {
    return fiber.child
  }

  /**
   * 如果存在同级 返回同级 构建同级的子级
   * 如果同级不存在 返回到父级 看父级是否有同级
   */
  let currentExecutelyFiber = fiber

  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
      currentExecutelyFiber.effects.concat([currentExecutelyFiber])
    )
    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling
    }
    currentExecutelyFiber = currentExecutelyFiber.parent
  }
  pendingCommit = currentExecutelyFiber
}

Fiber第二阶段

在workLoop方法中,while循环结束后,需要判断有没有pendingCommit,执行commmitAllWork方法

const commitAllWork = fiber => {
  /**
   * 循环 effets 数组 构建 DOM 节点树
   */
  fiber.effects.forEach(item => {
    if (item.tag === "class_component") {
      item.stateNode.__fiber = item
    }

    if (item.effectTag === "delete") {
      item.parent.stateNode.removeChild(item.stateNode)
    } else if (item.effectTag === "update") {
      /**
       * 更新
       */
      if (item.type === item.alternate.type) {
        /**
         *  节点类型相同
         */
        updateNodeElement(item.stateNode, item, item.alternate)
      } else {
        /**
         * 节点类型不同
         */
        item.parent.stateNode.replaceChild(
          item.stateNode,
          item.alternate.stateNode
        )
      }
    } else if (item.effectTag === "placement") {
      /**
       * 向页面中追加节点
       */
      /**
       * 当前要追加的子节点
       */
      let fiber = item
      /**
       * 当前要追加的子节点的父级
       */
      let parentFiber = item.parent
      /**
       * 找到普通节点父级 排除组件父级
       * 因为组件父级是不能直接追加真实DOM节点的
       */
      while (
        parentFiber.tag === "class_component" ||
        parentFiber.tag === "function_component"
      ) {
        parentFiber = parentFiber.parent
      }
      /**
       * 如果子节点是普通节点 找到父级 将子节点追加到父级中
       */
      if (fiber.tag === "host_component") {
        parentFiber.stateNode.appendChild(fiber.stateNode)
      }
    }
  })
  /**
   * 备份旧的 fiber 节点对象
   */
  fiber.stateNode.__rootFiberContainer = fiber
}


const workLoop = deadline => {
  /**
   * 如果子任务不存在 就去获取子任务
   */
  if (!subTask) {
    subTask = getFirstTask()
  }
  /**
   * 如果任务存在并且浏览器有空余时间就调用
   * executeTask 方法执行任务 接受任务 返回新的任务
   */
  while (subTask && deadline.timeRemaining() > 1) {
    subTask = executeTask(subTask)
  }

  if (pendingCommit) {
    commitAllWork(pendingCommit)
  }
}

渲染类组件

react下创建Component文件夹index.js

import { scheduleUpdate } from "../reconciliation"

export class Component {
  constructor(props) {
    this.props = props
  }
  setState(partialState) {
    scheduleUpdate(this, partialState)
  }
}

在getTag下找到如果是类组件时,导入处理类组件

import { Component } from "../../Component"

const getTag = vdom => {
  if (typeof vdom.type === "string") {
    return "host_component"
  } else if (Object.getPrototypeOf(vdom.type) === Component) {
    return "class_component"
  } else {
    return "function_component"
  }
}
export default getTag

处理stateNode属性,在createStateNode下处理

import { createDOMElement } from "../../DOM"
import { createReactInstance } from "../createReactInstance"

const createStateNode = fiber => {
  if (fiber.tag === "host_component") {
    return createDOMElement(fiber)
  } else {
    return createReactInstance(fiber)
  }
}

export default createStateNode

类组件创建fiber.type

export const createReactInstance = fiber => {
  let instance = null
  if (fiber.tag === "class_component") {
    instance = new fiber.type(fiber.props)
  } else {
    instance = fiber.type
  }
  return instance
}

如果是类组件,不能直接提交,需要通过while循环,找到他父级的父级,就是普通节点,通过reconcileChildren方法处理

处理函数组件

在executeTask方法

// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
  /**
   * 构建子级fiber对象
   */
  if (fiber.tag === "class_component") {
    if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
      fiber.stateNode.state = {
        ...fiber.stateNode.state,
        ...fiber.stateNode.__fiber.partialState
      }
    }

    reconcileChildren(fiber, fiber.stateNode.render())
  } else if (fiber.tag === "function_component") {
    reconcileChildren(fiber, fiber.stateNode(fiber.props))
  } else {
    reconcileChildren(fiber, fiber.props.children)
  }
  /**
   * 如果子级存在 返回子级
   * 将这个子级当做父级 构建这个父级下的子级
   */
  if (fiber.child) {
    return fiber.child
  }

  /**
   * 如果存在同级 返回同级 构建同级的子级
   * 如果同级不存在 返回到父级 看父级是否有同级
   */
  let currentExecutelyFiber = fiber

  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(
      currentExecutelyFiber.effects.concat([currentExecutelyFiber])
    )
    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling
    }
    currentExecutelyFiber = currentExecutelyFiber.parent
  }
  pendingCommit = currentExecutelyFiber
}

在 reconcileChildren 接收传递的函数组件值和属性

实现节点的更新操作

当dom渲染之后,要保存旧的节点对象,在创建新的Fiber节点对象,在创建新的节点,要看旧的存不存在,存在执行更新操作,执行更新的操作对象

在commitAllWork中找到对应的节点对象

const commitAllWork = fiber => {
  /**
   * 循环 effets 数组 构建 DOM 节点树
   */
  fiber.effects.forEach(item => {
    if (item.tag === "class_component") {
      item.stateNode.__fiber = item
    }

    if (item.effectTag === "delete") {
      item.parent.stateNode.removeChild(item.stateNode)
    } else if (item.effectTag === "update") {
      /**
       * 更新
       */
      if (item.type === item.alternate.type) {
        /**
         *  节点类型相同
         */
        updateNodeElement(item.stateNode, item, item.alternate)
      } else {
        /**
         * 节点类型不同
         */
        item.parent.stateNode.replaceChild(
          item.stateNode,
          item.alternate.stateNode
        )
      }
    } else if (item.effectTag === "placement") {
      /**
       * 向页面中追加节点
       */
      /**
       * 当前要追加的子节点
       */
      let fiber = item
      /**
       * 当前要追加的子节点的父级
       */
      let parentFiber = item.parent
      /**
       * 找到普通节点父级 排除组件父级
       * 因为组件父级是不能直接追加真实DOM节点的
       */
      while (
        parentFiber.tag === "class_component" ||
        parentFiber.tag === "function_component"
      ) {
        parentFiber = parentFiber.parent
      }
      /**
       * 如果子节点是普通节点 找到父级 将子节点追加到父级中
       */
      if (fiber.tag === "host_component") {
        parentFiber.stateNode.appendChild(fiber.stateNode)
      }
    }
  })
  /**
   * 备份旧的 fiber 节点对象
   */
  fiber.stateNode.__rootFiberContainer = fiber
}

更新根节点对象的地方,就是getFirstTask方法的alternate

const getFirstTask = () => {
  /**
   * 从任务队列中获取任务
   */
  const task = taskQueue.pop()

  if (task.from === "class_component") {
    const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: "host_root",
      effects: [],
      child: null,
      alternate: root
    }
  }

  /**
   * 返回最外层节点的fiber对象
   */
  return {
    props: task.props,
    stateNode: task.dom,
    tag: "host_root",
    effects: [],
    child: null,
    alternate: task.dom.__rootFiberContainer
  }
  }

在DOM下的updateNodeElement.js中处理

export default function updateNodeElement(
  newElement,
  virtualDOM,
  oldVirtualDOM = {}
) {
  // 获取节点对应的属性对象
  const newProps = virtualDOM.props || {}
  const oldProps = oldVirtualDOM.props || {}

  if (virtualDOM.type === "text") {
    if (newProps.textContent !== oldProps.textContent) {
      if (virtualDOM.parent.type !== oldVirtualDOM.parent.type) {
        virtualDOM.parent.stateNode.appendChild(
          document.createTextNode(newProps.textContent)
        )
      } else {
        virtualDOM.parent.stateNode.replaceChild(
          document.createTextNode(newProps.textContent),
          oldVirtualDOM.stateNode
        )
      }
    }
    return
  }

  Object.keys(newProps).forEach(propName => {
    // 获取属性值
    const newPropsValue = newProps[propName]
    const oldPropsValue = oldProps[propName]
    if (newPropsValue !== oldPropsValue) {
      // 判断属性是否是否事件属性 onClick -> click
      if (propName.slice(0, 2) === "on") {
        // 事件名称
        const eventName = propName.toLowerCase().slice(2)
        // 为元素添加事件
        newElement.addEventListener(eventName, newPropsValue)
        // 删除原有的事件的事件处理函数
        if (oldPropsValue) {
          newElement.removeEventListener(eventName, oldPropsValue)
        }
      } else if (propName === "value" || propName === "checked") {
        newElement[propName] = newPropsValue
      } else if (propName !== "children") {
        if (propName === "className") {
          newElement.setAttribute("class", newPropsValue)
        } else {
          newElement.setAttribute(propName, newPropsValue)
        }
      }
    }
  })
  // 判断属性被删除的情况
  Object.keys(oldProps).forEach(propName => {
    const newPropsValue = newProps[propName]
    const oldPropsValue = oldProps[propName]
    if (!newPropsValue) {
      // 属性被删除了
      if (propName.slice(0, 2) === "on") {
        const eventName = propName.toLowerCase().slice(2)
        newElement.removeEventListener(eventName, oldPropsValue)
      } else if (propName !== "children") {
        newElement.removeAttribute(propName)
      }
    }
  })
}

上一篇:DDL(数据定义语言)


下一篇:MyBatis