React - Fiber原理

凯特林 等级 708 1 2

浏览器渲染

屏幕刷新率(FPS)

  • 浏览器的正常绘制频率是60次/秒,小于这个值时,用户会感觉到卡顿
  • 绘制一次的称为一帧,平均每帧16.6ms

  • 每个帧的开头包括样式计算、布局和绘制
  • js的执行是单线程,js引擎和页面渲染引擎都占用主线程,GUI渲染和Javascript执行两者是互斥的
  • 如果某个js任务执行时间过长,浏览器会推迟渲染,每帧的绘制时间超过16.6ms,造成页面卡顿
  • requestAnimationFrame回调函数会在绘制之前执行
  • 在绘制之后,如果还有剩余时间,会执行 requestIdleCallback

React - Fiber原理

requestIdleCallback

  • requestIdleCallback使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应
  • 正常帧任务完成后没超过16.6ms,说明时间有富余,此时就会执行 requestIdleCallback 里注册的任务
  • requestIdleCallback在部分低版本浏览器中不支持,React内部是通过MessageChannel来实现的

React - Fiber原理

<script>
        function sleep(d) {
            for (var t = Date.now(); Date.now() - t <= d;);
        }
        const works = [
            () => {
                console.log("第1个任务开始");
                sleep(20);//sleep(20);
                console.log("第1个任务结束");
            },
            () => {
                console.log("第2个任务开始");
                sleep(20);//sleep(20);
                console.log("第2个任务结束");
            },
            () => {
                console.log("第3个任务开始");
                sleep(20);//sleep(20);
                console.log("第3个任务结束");
            },
        ];

        requestIdleCallback(workLoop, { timeout: 1000 });
        function workLoop(deadline) {
            console.log('本帧剩余时间', parseInt(deadline.timeRemaining()));
            while ((deadline.timeRemaining() > 1 || deadline.didTimeout) && works.length > 0) {
                performUnitOfWork();
            }

            if (works.length > 0) {
                console.log(`只剩下${parseInt(deadline.timeRemaining())}ms,时间片到了等待下次空闲时间的调度`);
                requestIdleCallback(workLoop);
            }
        }
        function performUnitOfWork() {
            works.shift()();
        }
    </script> 

Fiber 解决了什么问题

React的渲染分为协调和提交两个阶段,协调就是遍历dom树,进行domdiff,收集差异的阶段。提交就是修改真实dom,进行页面绘制的阶段。

Fiber之前的协调

  • React 会递归遍历节点,比对VirtualDOM树,找出需要变动的节点,然后同步更新它们。这个过程 React 称为Reconcilation(协调)
  • 在Reconcilation期间,React 会一直占用着浏览器资源,一则会导致用户触发的事件得不到响应, 二则会导致掉帧,用户可能会感觉到卡顿

Fiber的协调

  • fiber使用链表数据结构,通过浏览器requestIdleCallbackapi,让协调过程实现了可中断执行,分片完成协调任务
  • 在浏览器空闲时去执行协调过程,遍历节点,收集变动的节点,避免了界面卡顿

什么是 React fiber

Fiber是一种调度策略

  • 我们可以通过某些调度策略合理分配CPU资源,从而提高用户的响应速度
  • 通过Fiber架构,让自己的协调过程变成可被中断。 适时地让出CPU执行权,让浏览器及时地响应用户的交互

Fiber是一个执行单元

  • Fiber是一个执行单元,每次执行完一个执行单元, React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去

React - Fiber原理

Fiber是一种数据结构

  • React Fiber采用的链表的数据结构
  • 内部会将虚拟Dom转化成Fiber节点,通过链表结构将整个dom树表示出来
  • Fiber树采用孩子兄弟链表表示法,父节点的child指向第一个子节点,子节点的sibling指向下一个兄弟节点,子节点的return指向父节点
let fiberNode = {
    tag, // fiber类型  Host/Root
    type, // 虚拟dom的类型 div/span, 如果是类组件,这里是类名
    props, // 虚拟dom的属性
    stateNode, // 真实dom,如果是类组件,这里是类的实例
    // 构建链表的三个指针
    child, // 指向大儿子
    sibling, // 指向二弟
    return, // 指向父节点
    // 构建Effect List的指针
    firstEffect, // 第一个有副作用的子节点
    nextEffect,
    lastEffect,
    effectTag, // 副作用类型 插入、更新、删除

} 

React - Fiber原理

React fiber的调度顺序

react执行阶段

  • 每次渲染有两个阶段:Reconciliation(协调阶段)和Commit(提交阶段)
  • 协调阶段: 可以认为是 Diff 阶段, 这个阶段可以被中断, 这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等, 这些变更React 称之为副作用(Effect)
  • 提交阶段: 将上一个阶段收集的需要处理的副作用(Effects)一次性执行,将修改应用到真实Dom节点。这个阶段必须同步执行,不能被打断

构建Fiber树

  • 通过babel打包编译,将JSX元素转化为React.createElement()方法
  • render阶段,创建虚拟Dom节点,通过虚拟Dom创建fiber节点,构建Fiber链表

Fiber树的遍历和完成顺序

  • 从顶点开始遍历
  • 如果有第一个儿子,先遍历第一个儿子
  • 如果没有第一个儿子,标志着此节点遍历完成
  • 如果有弟弟遍历弟弟
  • 如果有没有下一个弟弟,返回父节点标识完成父节点遍历,如果有叔叔遍历叔叔
  • 没有父节点遍历结束
  • 遍历顺序:A1 B1 C1 C2 B2
  • 完成顺序:C1 C2 B1 B2 A1

React - Fiber原理

let A1 = { type: 'div', key: 'A1' };
let B1 = { type: 'div', key: 'B1', return: A1 };
let B2 = { type: 'div', key: 'B2', return: A1 };
let C1 = { type: 'div', key: 'C1', return: B1 };
let C2 = { type: 'div', key: 'C2', return: B1 };
A1.child = B1;
B1.sibling = B2;
B1.child = C1;
C1.sibling = C2;
// 根fiber
const rootFiber = A1;

//下一个工作单元
let nextUnitOfWork = null;
//render工作循环
function workLoop() {
    while (nextUnitOfWork) {
        //执行一个任务并返回下一个任务
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    //render阶段结束
}
function performUnitOfWork(fiber) {
    beginWork(fiber);
    if (fiber.child) {//如果子节点就返回第一个子节点
        return fiber.child;
    }
    while (fiber) {//如果没有子节点说明当前节点已经完成了渲染工作
        completeUnitOfWork(fiber);//可以结束此fiber的渲染了 
        if (fiber.sibling) {//如果它有弟弟就返回弟弟
            return fiber.sibling;
        }
        fiber = fiber.return;//如果没有弟弟让爸爸完成,然后找叔叔
    }
}
function beginWork(fiber) {
    console.log('beginWork', fiber.key);
    //fiber.stateNode = document.createElement(fiber.type);
}
function completeUnitOfWork(fiber) {
    console.log('completeUnitOfWork', fiber.key);
}
nextUnitOfWork = rootFiber;

// 请求浏览器分配空闲时间片,执行任务
requestIdleCallback(workLoop, { timeout: 1000 }); 

收集Effect List

  • 遍历Fiber树将有副作用的fiber节点收集起来,形成一个单向链表
  • 遍历完成后通过 commitWork方法,将收集的副作用进行提交,修改真实dom
  • Effect List的顺序和fiber节点遍历的完成顺序一致

React - Fiber原理

let container = document.getElementById('root');
let C1 = { type: 'div', key: 'C1', props: { id: 'C1', children: [] } };
let C2 = { type: 'div', key: 'C2', props: { id: 'C2', children: [] } };
let B1 = { type: 'div', key: 'B1', props: { id: 'B1', children: [C1, C2] } };
let B2 = { type: 'div', key: 'B2', props: { id: 'B2', children: [] } };
let A1 = { type: 'div', key: 'A1', props: { id: 'A1', children: [B1, B2] } };

let nextUnitOfWork = null;
let workInProgressRoot = null;

// 1. 浏览器空闲执行
function workLoop() {
    let shouldYield = false;//是否要让出时间片或者说控制权
    while (nextUnitOfWork && !shouldYield) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);//执行完一个任务后
        shouldYield = deadline.timeRemaining() < 1;//没有时间的话就要让出控制权
    }
    if (!nextUnitOfWork && workInProgressRoot) {
        console.log('render阶段结束');
        commitRoot();
    }

    //不管有没有任务,都请求再次调度 每一帧都要执行一次workLoop,检查看有没有要执行的任务
    requestIdleCallback(workLoop, { timeout: 500 });

}

// 5. 提交变更
function commitRoot() {
    let fiber = workInProgressRoot.firstEffect;
    while (fiber) {
        console.log(fiber.key); //C1 C2 B1 B2 A1
        commitWork(fiber);
        fiber = fiber.nextEffect;
    }
    workInProgressRoot = null;
}

// 6. 修改真实dom
function commitWork(currentFiber) {
    currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
}

// 2. 执行fiber工作
function performUnitOfWork(fiber) {
    beginWork(fiber);
    if (fiber.child) {
        return fiber.child;
    }
    while (fiber) {
        // 完成fiber工作
        completeUnitOfWork(fiber);
        if (fiber.sibling) {
            return fiber.sibling;
        }
        fiber = fiber.return;
    }
}

// 3. 开始工作
function beginWork(currentFiber) {
    if (!currentFiber.stateNode) {
        currentFiber.stateNode = document.createElement(currentFiber.type);//创建真实DOM
        for (let key in currentFiber.props) {//循环属性赋赋值给真实DOM
            if (key !== 'children' && key !== 'key')
                currentFiber.stateNode.setAttribute(key, currentFiber.props[key]);
        }
    }
    let previousFiber;
    // 创建子fiber
    currentFiber.props.children.forEach((child, index) => {
        let childFiber = {
            tag: 'HOST',
            type: child.type,
            key: child.key,
            props: child.props,
            return: currentFiber,
            effectTag: 'PLACEMENT',
            nextEffect: null
        }
        if (index === 0) {
            currentFiber.child = childFiber;
        } else {
            previousFiber.sibling = childFiber;
        }
        previousFiber = childFiber;
    });
}

//4. 完成fiber工作,收集effect List
function completeUnitOfWork(currentFiber) {
    const returnFiber = currentFiber.return;
    if (returnFiber) {
        if (!returnFiber.firstEffect) {
            returnFiber.firstEffect = currentFiber.firstEffect;
        }
        if (currentFiber.lastEffect) {
            if (returnFiber.lastEffect) {
                returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
            }
            returnFiber.lastEffect = currentFiber.lastEffect;
        }

        if (currentFiber.effectTag) {
            if (returnFiber.lastEffect) {
                returnFiber.lastEffect.nextEffect = currentFiber;
            } else {
                returnFiber.firstEffect = currentFiber;
            }
            returnFiber.lastEffect = currentFiber;
        }
    }
}
console.log(container);

workInProgressRoot = {
    key: 'ROOT',
    stateNode: container,
    props: { children: [A1] }
};
nextUnitOfWork = workInProgressRoot;//从RootFiber开始,到RootFiber结束

// 请求浏览器分配空闲时间片,执行任务
requestIdleCallback(workLoop, { timeout: 1000 }); 
收藏
评论区

相关推荐

25、react入门教程
25、react入门教程 25、react入门教程 0. React介绍 0.1 什么是React? React(有时称为React.js 或ReactJS)是一
Create React App
Create React App Create React App Facebook开源了React前端框架(MIT Licence),也同时提供了React脚手架 createreactapp(ht
从中断机制看 React Fiber 技术
带你了解计算机的中断机制(操作系统心脏)是如何提在 React Fiber 中应用及提高了页面渲染性能和用户体验。 前言 React 16 开始,采用了 Fiber 机制替代了原有的同步渲染 VDOM 的方案,提高了页面渲染性能和用户体验。Fiber 究竟是什么,网上也有很多优秀的技术揭秘文章,本篇主要想从计算机的中断机制来聊聊 React Fiber 技术
Omi架构与React Fiber
写在前面 Omi框架(https://github.com/AlloyTeam/omi)在架构设计的时候就决定把update的控制权交给了开发者,视灵活性比生命还重要。不然的话,如果遇到React Fiber要解决的这类问题的话,就需要
React - Fiber原理
浏览器渲染 屏幕刷新率(FPS) 浏览器的正常绘制频率是60次/秒,小于这个值时,用户会感觉到卡顿 绘制一次的称为一帧,平均每帧16.6ms 帧 每个帧的开头包括样式计算、布局和绘制 js的执行是单线程,js引擎和页面渲染引擎都占用主线程,GUI渲染和Javascript执行两者是互斥的 如果某个js任务执行时间过长,浏览器会推迟渲染,每
React Hook
用React Hook写一个简单的登录表单示例,两种方式: 第一种: import React, { useState } from "react"; import ReactDOM from "react-dom"; function LoginForm() { const [username, setUsern
React Native第三方组件和示例链接
以下是React Native的链接,有需要第三方组件或者示例的小伙伴可以收藏一下 01、React Native之Tab-View:https://js.coach/react-native/react-native-tab-view 02、React Native之正在加载Loading条:https://js.coach/react-native/
React 小白初入门
推荐学习: * React 官方文档: [https://react.docschina.org/](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Freact.docschina.org%2F) * React 菜鸟教程: [https://
React 架构的演变
前面的文章分析了 Concurrent 模式下异步更新的逻辑,以及 Fiber 架构是如何进行时间分片的,更新过程中的很多内容都省略了,评论区也收到了一些同学对更新过程的疑惑,今天的文章就来讲解下 React Fiber 架构的更新机制。 Fiber 数据结构 ---------- 我们先回顾一下 Fiber 节点的数据结构(之前文章省略了一部分属性,所
React 设计思想
https://github.com/react-guide/react-basic React 设计思想 ========== > 译者序:本文是 React 核心开发者、有 React API 终结者之称的 Sebastian Markbåge 撰写,阐述了他设计 React 的初衷。阅读此文,你能站在更高的高度思考 React 的过去、现在和未来。
React.render和reactDom.render的区别
这个是react最新版api,也就是0.14版本做出的改变。主要是为了使React能在更多的不同环境下更快、更容易构建。于是把react分成了react和react-dom两个部分。这样就为web版的react和移动端的React Native共享组件铺平了道路。也就是说我们可以跨平台使用相同的react组件。  新的react包包含了React.crea
React动画:react
#### 1.安装 yarn add react-transition-group #### 2.使用CSSTransition组件 import React, { PureComponent } from 'react'; import {
React应用渲染界面的入口
jsx代码: var React = require('react'); var ReactDOM = require('react-dom'); var MyButtonController = require('./components/MyButtonController'); ReactDOM.render
React技术栈实现XX点评电商App
> 项目地址:[https://github.com/Nealyang/React-Fullstack-Dianping-Demo](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fgithub.com%2FNealyang%2FReact-Fullstack-Dianping-Demo)
React:react
使用react构建单页面应用:   实现方法:(1)react-router        (2)react-router-dom `react-router`: 实现了路由的核心功能,而react-router-dom依赖react-router, `react-router-dom`: 基于`react-router`,加入了在浏览器运行环境下的