React Fiber 原理实现

本文介绍了React 16引入的Fiber技术,旨在解决React 16之前更新DOM时可能导致的性能问题。Fiber通过任务拆分、在浏览器空闲时间执行任务以及循环模拟递归来提高性能。文章详细讲解了Fiber的核心思想、实现思路,包括构建Fiber、提交Commit以及模拟实现的过程,如任务队列、空闲时间利用、requestIdleCallback以及Fiber对象结构。此外,还介绍了如何通过模拟实现jsx到virtualDom的转化、初次渲染、渲染更新以及类组件setState更新的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

react16之前的问题

react16之前dom元素的更新采用递归遍历的方式来对比子节点。一旦进入到递归遍历,整个过程将不能被打断,如果dom树的层次比较深,整个对比过程将耗时较长。而js的运行和dom的渲染又是互斥的,所以很容易造成卡顿。

Fiber

fiber是react16采用的一种新的节点对比更新方法,是为了解决react16之前的问题而产生的。

核心思想

  1. 任务拆分,将任务才分成一个个小的任务
  2. 在浏览器空闲时间执行任务,避免长时间占用主线程
  3. 使用循环模拟递归,因为循环是可以中断的

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

  1. 构建fiber,这个过程可以中断
  2. 提交Commit,不可中断

初始渲染的过程:virtualDom --> fiber --> fiber[] --> Dom
Dom更新操作:newFiber vs oldFiber --> 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 比对时使用
}

hostRoot:根节点
hostComponent:非根节点
classComponent:类组件
functionComponent:函数组件

React Fiber的实现

首先了解一下浏览器空闲时间

浏览器空闲时间

页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时, 用户会感觉到卡顿。s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,如果每一帧执行的时间小于16ms,就说明浏览器有空余时间。

RequestIdleCallback

requestIdleCallback接收一个函数作为参数,将在浏览器的空闲时间执行函数。如果在执行函数的过程中,有更高优先级的任务需要执行,则立即停止执行函数,优先执行高级别的任务。

requestIdleCallback(function(deadline){
	// deadline.timeRemaining()获取浏览器空闲时间,返回一个数字,单位为ms
})

搭建模拟项目的结构

按下图创建项目的目录结构
在这里插入图片描述
依赖介绍

依赖项描述
webpack模块打包工具
webpack-cli打包命令行工具
webpack-node-externals打包服务器端模块时剔除 node_modules 文件夹中的模块
@babel/coreJavaScript 代码转换工具
@babel/preset-envbabel 预置,转换高级 JavaScript 语法
@babel/preset-reactbabel 预置,转换 JSX 语法
babel-loaderwebpack 中的 babel 工具加载器
nodemon监控服务端文件变化,重启应用
npm-run-all命令行工具,可以同时执行多个命令
express基于 node 平台的 web 开发框架

使用命令安装依赖:

npm install webpack webpack-cli webpack-node-externals @babel/core @babel/preset-env @babel/preset-react babel-loader nodemon npm-run-all -D
npm install express

配置webpack以及babel

// webpack.config.server.js
const path = require('path')
const nodeExternals = require('webpack-node-externals')

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

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

使用express开发一个简易的Web服务器

// server.js
const express = require('express')

const app = express()

// 静态资源处理
app.use(express.static('dist'))

// 定义模板
const template = `
  <html>
    <head>
      <title>React Fiber</title>
      <meta charset="utf-8" />
    </head>
    <body>
      <div id="root"></div>
      <script src="bundle.js"></script>
    </body>
  </html>
`
// 接收请求,并返回响应
app.get('*', (req, res) => {
  res.send(template)
})

// 监听8888端口启动服务
app.listen(8888, () => console.log('server is running...'))

配置启动命令

"scripts": {
  "start": "npm-run-all --parallel dev:*",
  "dev:server-complie": "webpack --config webpack.config.server.js --watch",
  "dev:server": "nodemon ./build/server.js",
  "dev:client-compile": "webpack --config webpack.config.client.js --watch"
}

fiber模拟实现

将jsx转化为virtualDom
首先需要创建一个createElement方法来创建virtualDom,并在react/index.js中导出。babel在转换jsx时会调用createElement方法将jsx编译为virtualDom对象。createElement的创建可以参考React15 核心原理模拟实现

初次渲染

初次渲染流程:

  1. 调用render方法,向任务队列中添加一个任务,并指定在浏览器空闲时执行任务。

    export const render = (vdom, container) => {
      // 向任务队列中添加一项任务
      taskQueue.push({
        dom: container,
        props: { children: vdom }
      })
    
      // 在浏览器空闲时执行任务
      requestIdleCallback(preformTask)
    }
    
  2. 调用preformTask函数启动任务执行,并添加任务中断后,浏览器空闲时继续执行任务的处理。

    // 指定在浏览器空闲时执行任务,以及任务中断后的继续执行
    const preformTask = deadline => {
      workLoop(deadline)
      // 任务中断之后,浏览器空闲时继续执行任务
      if (subTask && !taskQueue.isEmpty()) {
        requestIdleCallback(performTask)
      }
    }
    
  3. 调用workLoop开始执行任务。

    const workLoop = deadline => {
      // 从任务队列的第一项任务获取子任务
      if (!subTask) {
        subTask = getFirstTask()
      }
    
      // 构建所有子节点的fiber
      while(subTask && deadline.timeRemaining() > 1) {
        subTask = executeTask(subTask)
      }
    
      // 所有节点的fiber对象构建完毕之后,进入提交阶段
      if (pendingCommit) {
        commitAllWork(pendingCommit)
      }
    }
    
  4. 首先调用getFirstTask从任务队列中获取一项任务,并返回其子任务(一个fiber对象)。

    // 构建任务开始时的根fiber对象,将从此fiber触发构建fiber树
    const getFirstTask = () => {
      // 取出 taskQueue 中的第一项任务
      const task = taskQueue.pop()
      // 构建并返回根 fiber 对象
      return {
        props: task.props,
        stateNode: task.dom,
        tag: 'host_root',
        effects: [],
        child: null
      }
    }
    
  5. 然后进入构建阶段,通过循环调用executeTask执行子任务,构建子节点的fiber对象。

    const executeTask = fiber => {
      if (fiber.tag === TAG.CLASSCOMPONENT) {
        // 类组件的子节点需要通过实例的render方法获取
        reconcileChildren(fiber, fiber.stateNode.render())
      } else if (fiber.tag === TAG.FUNCTIONCOMPONENT) {
        // 函数组件的子节点需要通过调用函数获取
        reconcileChildren(fiber, fiber.stateNode(fiber.props))
      } else {
        reconcileChildren(fiber, fiber.props.children)
      }
      
      // 继续构建子节点的子节点fiber
      if (fiber.child) {
        return fiber.child
      }
    
      let currentExecutelyFiber = fiber
      while(currentExecutelyFiber.parent) {
        // 合并子节点的effects已经子节点fiber。最终将所有节点的fiber合并到rootFiber的effects中
        currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat([currentExecutelyFiber]))
    
        // 构建其他节点的fiber
        if (currentExecutelyFiber.sibling) {
          return currentExecutelyFiber.sibling
        }
        // 没有兄弟节点,回退到父节点
        currentExecutelyFiber = currentExecutelyFiber.parent
      }
      
      // 所以节点的fiber构建完毕之后,currentExecutelyFiber将回退到根fiber对象,将其设置为将要提交的fiber
      pendingCommit = currentExecutelyFiber
    }
    
    

    将fiber的tag和effectTag作为常量值单独声明管理

    	/**
     * fiber tag
     */
    export const TAG = {
      HOMECOMPONENT: 'home_component', // 普通元素
      CLASSCOMPONENT: 'class_component', // 类组件
      FUNCTIONCOMPONENT: 'function_component' // 函数组件
    }
    
    /**
     * fiber effectTag
     */
    export const EFFECTTAG = {
      PLACEMENT: 'placement', // 新增
      UPDATE: 'update', // 修改
      DELETE: 'delete' // 删除
    }
    
    // 组件更新任务标识
    export const COMPONENTSTATEUPDATE = 'class_component'
    

    在调用reconcileChildren构建子节点fiber时需要判断节点的类型,类型不同,其子节点的virtualDom获取方式不同。

    // 为子节点构建fiber对象
    const reconcileChildren = (fiber, children) => {
      // 只有一个子节点时children为一个对象,多个子节点children为数组
      const arrifiedChildren = arrified(children)
    
      const len = arrifiedChildren.length
      let i = 0
      let prevFiber = null
      // 遍历子节点,为子节点创建fiber对象
      while(i < len) {
        const child = arrifiedChildren[i]
        const newFiber = {
          props: child.props,
          type: child.type,
          tag: getTag(child),
          effects: [],
          effectTag: EFFECTTAG.PLACEMENT,
          parent: fiber,
        }
    
        newFiber.stateNode = createStateNode(newFiber)
    
        if (i === 0) { // 第一个子节点,设置为fiber的child
          fiber.child = newFiber
        } else { // 设置为上一个fiber的兄弟 fiebr
          prevFiber.sibling = newFiber
        }
        // 更新 prevFiber 和 i
        prevFiber = newFiber
        i++
      }
    }
    
    import { Component } from '../core';
    import { TAG } from './common';
    
    export default function getTag(vdom) {
      if (typeof vdom.type === 'string') { // 普通节点
        return TAG.HOMECOMPONENT
      } else if (Object.getPrototypeOf(vdom.type) === Component) { // 类组件
        return TAG.CLASSCOMPONENT
      } else {
        return TAG.FUNCTIONCOMPONENT
      }
    }
    
    import { createDOMElement } from '../dom';
    import { TAG } from './common';
    import createComponentInstance from './createComponentInstance';
    /**
     * 创建fiber对象的stateNode
     * 1、普通节点时,stateNode存储节点对应的真实Dom
     * 2、类组件时,stateNode存储组件实例
     * 3、函数组件时,stateNode存储函数组件本身
     * @param {*} fiber 
     */
    export default function createStateNode(fiber) {
      if (fiber.tag === TAG.HOMECOMPONENT) {
        return createDOMElement(fiber)
      } else {
        return createComponentInstance(fiber)
      }
    }
    
    import updateElementNode from './updateElementNode'
    
    export default function createDOMElement(virtualDom) {
      let newElement = null
      // 创建真实DOM
      if (virtualDom.type === 'text') { // 先判断是否是文本节点
        newElement = document.createTextNode(virtualDom.props.textContent)
      } else { // 元素节点
        newElement = document.createElement(virtualDom.type)
        // 将属性添加到生成的真实DOM中
        updateElementNode(newElement, virtualDom)
      }
    
      return newElement
    }
    
    import { TAG } from './common'
    
    /**
     * 创建组件的实例
     * @param {*} fiber 
     * @returns 
     */
    export default function createComponentInstance(fiber) {
      let instance = null
      if (fiber.tag === TAG.CLASSCOMPONENT) {
        instance = new fiber.type(fiber.props)
      } else {
        instance = fiber.type
      }
      return instance
    }
    

    构建子节点fiber对象时,首先会构建所有节点的第一个子节点的fiber对象,然后再从最底层的第一个子节点开始构建其兄弟节点,如果没有兄弟节点,则回退到父节点,构建父节点的兄弟节点。在构建兄弟节点的过程中,会遍历到所有节点,同时收集所有字节及其effects中的内容到父节点的effects中。最后回退到根fiber节点,此时根fiber节点的effects中保存了所有fiber节点。最后将此根fiber设置为待提交的fiber对象,进入到提交阶段。

  6. 所有节点fiber对象构建完毕之后进入到提交阶段。提交阶段会遍历fiber中的effects,根据操作类型effectTag判断操作类型,进行相应的dom操作。初始渲染阶段只有添加操作。将fiber.stateNode添加到parentFiber的stateNode中。添加时如果父fiber是组件,需要向上寻找第一个非组件的fiber。

    // 遍历pendingCommit对象中的effects,根据操作类型执行Dom操作
    const commitAllWork = fiber => {
      fiber.effects.forEach(item => {
        if (item.effectTag === EFFECTTAG.PLACEMENT) {
          let parentFiber = item.parent
          // 如果父节点是组件,则找上一个节点直到找到第一个不为组件的fiber
          while(parentFiber.tag === TAG.CLASSCOMPONENT || parentFiber.tag === TAG.FUNCTIONCOMPONENT) {
            parentFiber = parentFiber.parent
          }
    
          // 如果节点是普通节点,将当前元素追加到父级元素
          if (item.tag === TAG.HOMECOMPONENT) {
            parentFiber.stateNode.appendChild(item.stateNode)
          }
        }
      })
    }
    

渲染更新

要实现渲染更新首先要对rootContainer节点的fiber对象进行备份,这样在进行更新时可以根据这个fiber对象找到子节点对应的fiber对象。

rootContainer节点的fiber备份在初次渲染完毕时进行,即在commitAllWork最后。

// 遍历pendingCommit对象中的effects,根据操作类型执行Dom操作
const commitAllWork = fiber => {
  fiber.effects.forEach(item => {
    if (item.effectTag === EFFECTTAG.PLACEMENT) {
      let parentFiber = item.parent
      // 如果父节点是组件,则找上一个节点直到找到第一个不为组件的fiber
      while(parentFiber.tag === TAG.CLASSCOMPONENT || parentFiber.tag === TAG.FUNCTIONCOMPONENT) {
        parentFiber = parentFiber.parent
      }

      // 如果节点是普通节点,将当前元素追加到父级元素
      if (item.tag === TAG.HOMECOMPONENT) {
        parentFiber.stateNode.appendChild(item.stateNode)
      }
    }
  })

  // 将rootContainer的fiber备份到root dom节点上
  fiber.stateNode.__rootContainerFiber = fiber
}

在getFirstTask中设置rootContainer节点的fiber备份。在执行构建任务时可以根据是否有备份判断是否更新。

// 构建任务开始时的根fiber对象,将从此fiber触发构建fiber树
const getFirstTask = () => {
  // 取出 taskQueue 中的第一项任务
  const task = taskQueue.pop()
  // 构建并返回根 fiber 对象
  return {
    props: task.props,
    stateNode: task.dom,
    tag: 'host_root',
    effects: [],
    child: null,
    alternate: task.dom.__rootContainerFiber
  }
}

调用reconcileChildren构建子节点fiber时,首先取出rootContainer的fiber备份,然后通过该备份获取rootContainer子节点的fiber,再通过链表结构特性获取其他节点的fiber备份。

// 为子节点构建fiber对象
const reconcileChildren = (fiber, children) => {
  // 只有一个子节点时children为一个对象,多个子节点children为数组
  const arrifiedChildren = arrified(children)

  // 备份节点
  let alternate = null
  // 获取备份节点子节点,和下面遍历的元素对应
  if (fiber.alternate && fiber.alternate.child) {
    alternate = fiber.alternate.child
  }

  const len = arrifiedChildren.length
  let i = 0
  let newFiber = null
  let prevFiber = null
  // 遍历子节点,为子节点创建fiber对象
  while(i < len || alternate) {
    const child = arrifiedChildren[i]
    // 根据备份和对应节点判断是什么操作
    if (alternate && !child) { // 备份节点存在,对应节点不存在,表示要删除节点
      alternate.effectTag = EFFECTTAG.DELETE // 节点操作类型为删除
      fiber.effects.push(alternate)
    } else { // 更新或新增
      newFiber = {
        type: child.type,
        props: child.props,
        tag: getTag(child),
        effects: [],
        effectTag: EFFECTTAG.PLACEMENT,
        parent: fiber,
        alternate,
      }
      if (alternate && child && alternate.type === child.type) { // 备份节点和对应节点都存在,且类型一致时,不需要更新stateNode(即不用创建新的dom,使用原先的dom)
        newFiber.stateNode = alternate.stateNode
      } else { // 类型不同,或新增的情况都需要重新创建
        newFiber.stateNode = createStateNode(newFiber)
      }
      if (alternate && child) { // 修改操作类型为新增
        newFiber.effectTag = EFFECTTAG.UPDATE
      } else { // 修改操作类型为修改
        newFiber.effectTag = EFFECTTAG.PLACEMENT
      }
    }

    if (i === 0) { // 第一个子节点,设置为fiber的child
      fiber.child = newFiber
    } else if (child) { // 节点存在时,设置为上一个fiber的兄弟 fiebr
      prevFiber.sibling = newFiber
    }

    // 更新备份节点
    if (alternate && alternate.sibling) {
      alternate = alternate.sibling
    } else {
      alternate = null
    }

    // 更新 prevFiber 和 i
    prevFiber = newFiber
    i++
  }
}

在commitAllWork中添加更新处理,处理更新时有一些地方需要注意:

  1. 存在parentFiber为组件的情况,组件的stateNode对应的是组件实例,所以需要向上查找非组件的第一个fiber。

  2. 替换节点也可能找到备份节点为组件的情况,这时要向下查找组件的子节点不为组件的fiber,即实际渲染的根节点的fiber。

    // 更新前
    const jsx = (
    	<div>
    		<Hello title={this.props.title} />
    		<p>ppp</p>
    		<p>&hearts;</p>
    		<p>ppp</p>
    	</div>
    )
    // 更新后
    const updateJsx= (
    	<div>
          <p>{this.props.title}</p>
          <p>ssss</p>
          <p>&hearts;</p>
          <p>ssss</p>
        </div>
    )
    

    构建新的fiber时,第一个p的fiber备份为 Hello组件的fiber,此时实际要替换的是Hello的实际渲染内容

  3. 替换备份节点时还有一种情况,备份节点的父节点是组件,此时直接替换该节点显然是不对的,应该是其父节点去替换备份节点的父节点。这时我们应该直接将该节点添加到其父节点。

    以上面的实例为例,第一个p节点的文本节点的fiber会有一个备份fiber指向Hello的第一个子节点的fiber。显然p节点的文本节点不能替换Hello的第一个子节点。

  4. 添加节点到父节点时,为保证节点的顺序,应该将节点插入到节点对应备份的兄弟节点之前。

/**
 * 将dom插入到父级元素
 * @param {*} item 
 */
const appendToParent = item => {
  let parentFiber = item.parent
  // 如果父节点是组件,则找上一个节点直到找到第一个不为组件的fiber
  while(parentFiber.tag === TAG.CLASSCOMPONENT || parentFiber.tag === TAG.FUNCTIONCOMPONENT) {
    parentFiber = parentFiber.parent
  }

  // 如果节点是普通节点,将当前元素追加到父级元素
  if (item.tag === TAG.HOMECOMPONENT) {
    // 如果备份节点的兄弟节点存在,则将节点插入到该节点之前
    if (item.alternate && item.alternate.sibling) {
      parentFiber.stateNode.insertBefore(item.stateNode, item.alternate.sibling.stateNode)
    } else {
      parentFiber.stateNode.appendChild(item.stateNode)
    }
  }
}

/**
 * 找到组件中第一个不为组件的根节点。主要是为了处理组件嵌套组件的情况
 * function Parent() {
 *    return <Child />
 * }
 * @param {*} fiber 
 * @returns 
 */
const findNotComponentChildFiber = fiber => {
  let child = fiber
  while(isComponent(child)) {
    child = child.child
  }
  return child
}

const findNotComponentParentFiber = fiber => {
  let parent = fiber.parent
  while(isComponent(parent)) {
    parent = parent.parent
  }
  return parent
}

// 遍历pendingCommit对象中的effects,根据操作类型执行Dom操作
const commitAllWork = fiber => {
  fiber.effects.forEach(item => {
    if (item.effectTag === EFFECTTAG.DELETE) { // 节点删除
      item.parent.stateNode.removeChild(item.stateNode)
    } else if (item.effectTag === EFFECTTAG.UPDATE) { // 修改
      // 比较type是否发生变化
      if (item.type === item.alternate.type) { // 类型一致
        // 更新节点属性
        updateElementNode(item.stateNode, item, item.alternate)
      } else { // 类型不同,
        let parent = findNotComponentParentFiber(item)
        let replaceFiber = null
        if (isComponent(item.parent)) { // 父组件是组件,替换组件备份对应的节点
          replaceFiber = item.parent.alternate
        } else {
          replaceFiber = item.alternate
        }
        if (isComponent(item.alternate.parent)) { // 如果备份的父节点是组件,则直接将该节点添加到父节点,然后让父节点去替换组件的渲染内容
          appendToParent(item)
        } else {
          // 使用当前节点替换组件节点树中第一个不为组件的节点
          replaceFiber = findNotComponentChildFiber(replaceFiber)
          parent.stateNode.replaceChild(item.stateNode, replaceFiber.stateNode)
        }
      }
    } else if (item.effectTag === EFFECTTAG.PLACEMENT) {
      appendToParent(item)
    }
  })

  // 将rootContainer的fiber备份到root dom节点上
  fiber.stateNode.__rootContainerFiber = fiber
}

类组件setState更新

首先Component中需要定义setState方法。该方法会调用一个方法向任务队列中添加一个任务,并在浏览器空闲时执行任务。

export default class Component {
  constructor(props) {
    this.props = props
  }
  setState(particalState) {
    scheduleUpdate(this, particalState)
  }
  // 生命周期函数
  componentWillMount() {}
  componentDidMount() {}
  componentWillReceiveProps(nextProps) {}
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps != this.props || nextState != this.state
  }
  componentWillUpdate(nextProps, nextState) {}
  componentDidUpdate(prevProps, preState) {}
  componentWillUnmount() {}
}
/**
 1. 组件state更新任务
 2. @param {*} instance 
 3. @param {*} particalState 
 */
export const scheduleUpdate = (instance, particalState) => {
  // 向任务队列添加一项任务
  taskQueue.push({
    from: COMPONENTSTATEUPDATE, // 组件更新的标志
    instance,
    particalState
  })

  // 指定浏览器空闲时执行任务
  requestIdleCallback(preformTask)
}

类组件的state更新任务将从类组件实例的fiber出发:
要想在任务执行时获取组件实例的fiber,需要在初始渲染时将组件实例的fiber保存到组件实例上。这里选择在executeTask 的判断中将fiber记录到组件实例上。

const executeTask = fiber => {
  if (fiber.tag === TAG.CLASSCOMPONENT) {
    // 判断是否是state更新,如果是state更新,则合并state
    if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.particalState) {
      fiber.stateNode.state = Object.assign(fiber.stateNode.state, fiber.stateNode.__fiber.particalState)
    }
    // 将组件的fiber记录到组件的实例上
    fiber.stateNode.__fiber = fiber
    // 类组件的子节点需要通过实例的render方法获取
    reconcileChildren(fiber, fiber.stateNode.render())
  } else if (fiber.tag === TAG.FUNCTIONCOMPONENT) {
    // 函数组件的子节点需要通过调用函数获取
    reconcileChildren(fiber, fiber.stateNode(fiber.props))
  } else {
    reconcileChildren(fiber, fiber.props.children)
  }
  
  // 继续构建子节点的子节点fiber
  if (fiber.child) {
    return fiber.child
  }

  let currentExecutelyFiber = fiber
  while(currentExecutelyFiber.parent) {
    // 合并子节点的effects已经子节点fiber。最终将所有节点的fiber合并到rootFiber的effects中
    currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat(currentExecutelyFiber.tag === TAG.HOMECOMPONENT ? [currentExecutelyFiber] : []))

    // 构建其他节点的fiber
    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling
    }
    // 没有兄弟节点,回退到父节点
    currentExecutelyFiber = currentExecutelyFiber.parent
  }
  
  // 所以节点的fiber构建完毕之后,currentExecutelyFiber将回退到根fiber对象,将其设置为将要提交的fiber
  pendingCommit = currentExecutelyFiber
}

然后在getFirstTask时,判断如果是组件state更新任务,则从组件的fiber开始向上查找root节点对应的fiber。然后返回一个新的root节点的fiber。

// 构建任务开始时的根fiber对象,将从此fiber触发构建fiber树
const getFirstTask = () => {
  // 取出 taskQueue 中的第一项任务
  const task = taskQueue.pop()
  if (task.from === COMPONENTSTATEUPDATE) { // 处理state更新
    // 记录要更新的state
    task.instance.__fiber.particalState = task.particalState
    // 通过组件的fiber向上查找root的fiber
    let fiber = task.instance.__fiber
    while(fiber.parent) {
      fiber = fiber.parent
    }
    return {
      props: fiber.props,
      stateNode: fiber.stateNode,
      tag: 'host_root',
      effects: [],
      child: null,
      alternate: fiber
    }
  }
  // 构建并返回根 fiber 对象
  return {
    props: task.props,
    stateNode: task.dom,
    tag: 'host_root',
    effects: [],
    child: null,
    alternate: task.dom.__rootContainerFiber
  }
}

然后从新的fiber出发构建新的fibe树,构建类组件的子fiber树时,判断是否是正在进行state更新任务,如果是则合并state之后再调用render生成新的virtualDom,再去构建子fiber树。具体代码见上面的executeTask方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值