React Fiber 原理实现

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/core JavaScript 代码转换工具
@babel/preset-env babel 预置,转换高级 JavaScript 语法
@babel/preset-react babel 预置,转换 JSX 语法
babel-loader webpack 中的 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 =><
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值