<div id = "B1"><d...">
最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Fiber架构

    正文概述 掘金(Stoney_S)   2021-04-06   515

    一 Fiber之前的React

    下面代码实现了一个简单的react手写

    let element = (
      <div id = "A1">
        <div id = "B1">
          <div id="C1"></div>
          <div id="C2"></div>
        </div>
        <div id="B2"></div>
      </div>
    )
    
    console.log(JSON.stringify(element, null, 2))
    // 如果节点多,层级特别深
    // 因为js是单线程,而且ui渲染和js执行是互斥的
    
    function render(element, parentDom) {
      // 创建DOM元素
      let dom = document.createElement(element.type);
      // 处理属性
      Object.keys(element.props)
      .filter(key => key !== 'children')
      .forEach(key => {
        dom[key] = element.props[key];
      });
      if(Array.isArray(element.props.children)) {
        // 把每个子虚拟DOM变成真实DOM插入到DOM节点里
        element.props.children.forEach(child => render(child, dom));
      }
      parentDom.appendChild(dom);
    }
    
    render(
      element,
      document.getElementById('root')
    );
    

    打印出的element如下:

    {
      "type": "div",
      "key": null,
      "ref": null,
      "props": {
        "id": "A1",
        "children": [
          {
            "type": "div",
            "key": null,
            "ref": null,
            "props": {
              "id": "B1",
              "children": [
                {
                  "type": "div",
                  "key": null,
                  "ref": null,
                  "props": {
                    "id": "C1"
                  },
                  "_owner": null,
                  "_store": {}
                },
                {
                  "type": "div",
                  "key": null,
                  "ref": null,
                  "props": {
                    "id": "C2"
                  },
                  "_owner": null,
                  "_store": {}
                }
              ]
            },
            "_owner": null,
            "_store": {}
          },
          {
            "type": "div",
            "key": null,
            "ref": null,
            "props": {
              "id": "B2"
            },
            "_owner": null,
            "_store": {}
          }
        ]
      },
      "_owner": null,
      "_store": {}
    }
    

    效果如下:

    Fiber架构

    jsx标签化是嵌套的结构,如代码所示,最终会编译成递归执行的代码,要想中断递归是很困难的。即react16之前的调度器为栈调度器,栈浅显易懂,代码量少,但不能随意break掉,continue掉,要维护一系列的栈上下文;

    二 帧

    • 目前大多数设备的屏幕刷新频率为60次/秒
    • 当每秒绘制的帧数(FPS)达到60时,页面是流畅的,小于这个值时,用户会感觉到卡顿;
    • 每个帧的预算时间是16.66毫秒(1秒/60)
    • 每个帧的开头包括样式计算,布局和绘制
    • JavaScript执行JavaScript引擎和页面渲染引擎在同一个渲染线程,GUI渲染和JavaScript执行两者是互斥的
    • 如果某个任务执行时间过长,浏览器就会推迟渲染;

    三 什么是Fiber

    我们可以通过某些调度策略合理分配CPU资源,从而提高用户的响应速度

    通过Fiber架构,让自己的协调过程变成可被中断,适时地让出CPU执行权,可以让浏览器即使地响应用户的交互;

    fiber:就是一个数据结构,它有很多属性,虚拟dom是对真实dom的一种简化;一些真实的dom都做不到的事情,那虚拟dom更做不到,于是就有了fiber,有很多属性,希望借由fiber上的这堆属性来做一些比较厉害的事情;

    fiber架构

    为了弥补一些不足,就设计了一些新的算法,而为了能让这些算法跑起来,所以出现了fiber这种数据结构; fiber数据结构+算法 = fiber架构;

    react应用从始至终管理着基本的三样东西:

    1. Root(整个应用的根儿,一个对象,不是fiber,有个属性指向current树,同时也有个属性指向workInProgress树)

    2. current树(树上的每一个节点都是fiber,保存的是上一次的状态 并且每个fiber节点,都对应着一个jsx节点)

    3. workInProgress树(树上的每一个节点都是fiber,保存的是本次新的状态,并且每个fiber节点都对应一个jsx节点)

    初次渲染的时候,没有current树 react在一开始创建Root,就会同时创建一个unintialFiber的东西(未初始化的fiber) 让react的current指向了uninitialFiber 之后再去创建一个本次要用到的workInProgress

    react 中主要分两个阶段

    render阶段(指的是创建fiber的过程)

    1. 为每个节点创建新的fiber(workInProgress)(可能是复用) 生成一颗有新状态的workInProgress树

    2. 初次渲染的时候(或新创建了某个节点的时候) 会将这个fiber创建真实的dom实例 并且对当前节点的子节点进行插入appendChildren,

    3. 如果不是初次渲染的话,就对比新旧的fiber的状态,将产生了更新的fiber节点,最终通过链表的形式,挂载到RootFiber

    commit阶段****才是真正的要操作页面的阶段

    1. 执行生命周期

    2. 会从RootFiber上获取到那条链表,根据链表上的标识来操作页面;

    3.1 Fiber是一个执行单元

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

    Fiber架构

    3.2 Fiber是一种数据结构

    react目前的做法是使用链表,每个虚拟节点内部表示为一个Fiber;

    Fiber架构

    代码如下所示:

    class FiberNode {
      constructor(tag, key, pendingProps) {
        this.tag = tag; // 表示当前fiber的类型
        this.key = key;
        this.type = null // 'div' || 'h1' || Ding
        this.stateNode = null; // 表示当前fiber的实例
        this.child = null; // 表示当前fiber的子节点 每一个fiber有且只有一个指向它的firstChild
        this.sibling = null; // 表示当前节点的兄弟节点 每个fiber有且只有一个属性指向隔壁的兄弟节点
        this.return = null; // 表示当前fiber的父节点
        this.index = 0;
        this.memoizedState = null; // 表示当前fiber的state
        this.memoizedProps = null; // 表示当前fiber的props
        this.pendingProps = pendingProps; // 表示新进来的props
        this.effecTag = null; // 表示当前节点要进行何种更新
        this.firstEffect = null; // 表示当前节点的有更新的第一个子节点
        this.lastEffect = null; // 表示当前节点的有更新的最后一个子节点
        this.nextEffect = null; // 表示下一个要更新的子节点
    
        this.alternate = null; // 用来连接current和workInProgress的
        this.updateQueue = null; // 一条链表上面挂载的是当前fiber的新状态
    
        // 其实还有很多其他的属性
        // expirationTime: 0
      }
    }
    

    四 rAF

    requestAnimationFrame回调函数会在绘制之前执行

    • requestAnimationFrame(callback)会在浏览器每次重绘前执行callback回调,每次callback执行的时机都是浏览器刷新下一帧渲染周期的起点上

    • requestAnimationFrame(callback)的回调callback回调参数timestamp是回调被调用的时间,也就是当前帧的起始时间

    • rAfTime performance.timing.navigationStart + performance.now()约等于Date.now();

    下面代码实现了一个绘制进度条的功能;

    <script>
        let div = document.querySelector('div');
        let button = document.querySelector('button');
        let startTime;
        function progress() {
          div.style.width = div.offsetWidth + 1 +'px';
          div.innerHTML = div.offsetWidth + '%';
          if(div.offsetWidth < 100) {
            console.log(Date.now() - startTime + 'ms');
            startTime = Date.now();
            requestAnimationFrame(progress);
          }
        }
        button.onclick = function() {
          div.style.width = 0;
          startTime = Date.now();
          // 浏览器会在每一帧渲染前执行progress
          requestAnimationFrame(progress);
        }
      </script>
    

    五 requestIdleCallbac

    • 我们希望快速响应用户,让用户觉得够快,不能阻塞用户的交互

    • requestIdleCallback使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

    • 正常帧任务完成后没超过16ms,说明时间有富余,此时就会执行requestIdleCallback里注册的任务

    • requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务,而requestIdleCallback的回调则不一定,属于低优先级任务;

    Fiber架构

    <script type="text/javascript">
        function sleep(duration) {
          let start = Date.now();
          while(start + duration > Date.now()) {}
        }
        function progress() {
          console.log('progress');
          requestAnimationFrame(progress);
        }
        // requestAnimationFrame(progress);
        let channel = new MessageChannel();
        let activeFrameTime = 1000/60; // 16.6
        let frameDeadline; // 这一帧的截止时间
        let pendingCallback;
        let timeRemaining = () => frameDeadline - performance.now();
        channel.port2.onmessage = function() {
          let currentTime = performance.now();
          // 如果帧的截止时间已经小于当前时间,说明已经过期了
          let didTimeout = frameDeadline <= currentTime;
          if(didTimeout || timeRemaining() > 0) {
            if(pendingCallback) {
              pendingCallback({didTimeout, timeRemaining});
            }
          }
        }
        window.requestIdleCallback = (callback, options) => {
          requestAnimationFrame((rafTime) => {
            console.log('rafTime', rafTime);
            // 每一帧开始的时间加上16.6 就是一帧的截止时间了
            frameDeadline = rafTime + activeFrameTime;
            pendingCallback = callback;
            // 其实发消息只会,相当于添加一个宏任务
            channel.port1.postMessage('hello');
          });
        }
        const works = [
          () => {
            console.log('A1开始');
            sleep(20);
            console.log('A1结束');
          },
          () => {
            console.log('B1开始');
            sleep(20);
            console.log('B1结束');
          },
          () => {
            console.log('C1开始');
            sleep(20);
            console.log('C1结束');
          },
          () => {
            console.log('C2开始');
            sleep(20);
            console.log('C2结束');
          },
          () => {
            console.log('B2开始');
            sleep(20);
            console.log('B2结束');
          },
        ]
        // 告诉浏览器 你可以在空闲的时间执行任务,但是如果已经过期了 不管你有没有空 都要帮我执行
        requestIdleCallback(workLoop, {timeout: 1000});
        // 循环执行工具
        function workLoop(deadline) {
          console.log('本帧的剩余时间', parseInt(deadline.timeRemaining()));
          // 如果说还有剩余时间 并且还有没有完成的任务
          while((deadline.timeRemaining() > 0 || deadline.didTimeout) && works.length > 0){
            performUnitWork();
          }
          if(works.length > 0) {
            console.log(`只剩下${deadline.timeRemaining()}, 时间片已经到期,等待下次调试`);
            requestIdleCallback(workLoop);
          }
        }
        function performUnitWork() {
          let work = works.shift();
          work();
        }
    

    六 MessageChannel

    1. 目前requestIdleCallback只要Chrome支持

    2. 所以目前React利用MessageChannel模拟了requestIdleCallback,将回调延迟到绘制操作只后执行

    3. MessageChannel API允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据;

    4. MessageChannel创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,而一个端口只要绑定了,就能收到另一个端口传过来的数据

    5. MessageChannel是一个宏任务;

    七 Fiber执行阶段

    每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)

    • 协调阶段:可以认为是diff阶段,这个阶段可以被中断,这个阶段会找出所有节点变更,例如节点新增,删除,属性变更等等,这些变更React称之为副作用;
    • 提交阶段:将上一个阶段计算出来的需要处理的副作用(Effetcs)一次性执行了。这个阶段必须同步执行,不能被打断;

    7.1 render阶段

    1. 从顶点开始遍历
    2. 如果有第一个儿子,先遍历第一个儿子
    3. 如果没有第一个儿子,标志着此节点遍历完成
    4. 如果有弟弟遍历弟弟
    5. 如果没有下一个弟弟,返回父节点标志完成父节点遍历,如果有叔叔遍历叔叔
    6. 没有父节点遍历结束

    先儿子,后弟弟,再叔叔,辈分越小越优先

    什么时候一个节点遍历完成,没有子节点,或者所有子节点都遍历完成了,没爹了就表示全部遍历完成了;

    Fiber架构

    7.2 commit阶段

    下面代码实现了一个简易的Fiber架构,只有初次render过程;

    import element from './element.js';
    let container = document.getElementById('root');
    const PLACEMENT = 'PLACEMENT';
    
    // 下一个工作单元
    // fiber其实也是一个普通的JS对象
    let workInProgressRoot = {
      stateNode: container, // 此fiber对应的dom节点
      props: {children: [element]} // fiber的属性
    }
    
    let nextUnitOfWork = workInProgressRoot;
    
    function workLoop(deadline) {
      // 如果有当前的工作单元,就执行它,并返回一个工作单元
      while(nextUnitOfWork && deadline.timeRemaining() > 0) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
      if(!nextUnitOfWork) {
        commitRoot();
      }
    }
    
    function commitRoot() {
      let currentFiber = workInProgressRoot.firstEffect;
      while(currentFiber) {
        console.log('commitRoot', currentFiber.props.id);
        if(currentFiber.effectTag === 'PLACEMENT') {
          currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
        }
        currentFiber = currentFiber.nextEffect;
      }
      workInProgressRoot = null;
    }
    
    function performUnitOfWork(workingProgressFiber) {
      // 1 创建真实的dom, 并没有挂载 2,创建fiber子树
      beginWork(workingProgressFiber);
      if(workingProgressFiber.child) {
        return workingProgressFiber.child; // 如果有儿子,返回儿子
      }
      while(workingProgressFiber) {
        // 如果没有儿子当前节点其实就结束完成了
        completeUnitOfWork(workingProgressFiber);
        if(workingProgressFiber.sibling) { // 如果有弟弟,返回弟弟
          return workingProgressFiber.sibling;
        }
        workingProgressFiber = workingProgressFiber.return; // 先指向父亲
      }
    }
    
    function beginWork(workingProgressFiber) {
      console.log('beginWork', workingProgressFiber.props.id);
      if(!workingProgressFiber.stateNode) {
        workingProgressFiber.stateNode = document.createElement(workingProgressFiber.type);
        for (let key in workingProgressFiber.props) {
          if(key !== 'children') {
            workingProgressFiber.stateNode[key] = workingProgressFiber.props[key];
          }
        }
      }
      // 在beginwork里是不会挂载的
      let previousFiber;
      if(Array.isArray(workingProgressFiber.props.children)) {
        workingProgressFiber.props.children.forEach((child, index) => {
          let childFiber = {
            type: child.type,
            props: child.props,
            return: workingProgressFiber,
            effectTag: 'PLACEMENT', // 这个fiber对应的dom节点需要被插入到页面中
          }
          if(index === 0) {
            workingProgressFiber.child = childFiber;
          } else {
            previousFiber.sibling = childFiber;
          }
          previousFiber = childFiber;
        });
      }
    
    }
    
    function completeUnitOfWork(workingProgressFiber) {
      console.log('completeUnitOfWork', workingProgressFiber.props.id);
      // 构建副作用链effectList 只有那些有副作用的节点
      let returnFiber = workingProgressFiber.return;
      if(returnFiber) {
        // 把当前fiber的有副作用子链表挂到父亲身上
        if(!returnFiber.firstEffect) {
          returnFiber.firstEffect = workingProgressFiber.firstEffect;
        }
        if(workingProgressFiber.lastEffect) {
          if(returnFiber.lastEffect) {
            returnFiber.lastEffect.nextEffect = workingProgressFiber.firstEffect;
          }
          returnFiber.lastEffect = workingProgressFiber.lastEffect;
        }
        // 再把自己挂到后面
        if(workingProgressFiber.effectTag) {
          if(returnFiber.lastEffect) {
            returnFiber.lastEffect.nextEffect = workingProgressFiber;
          } else {
            returnFiber.firstEffect = workingProgressFiber;
          }
          returnFiber.lastEffect = workingProgressFiber;
        }
      }
    }
    
    // 告诉浏览器在空闲的时间执行workLoop
    requestIdleCallback(workLoop);
    

    上面代码可以实现第一章的dom结构;

    八 fiber架构本质

    循环条件:利用requestIdleCallback空闲时间递减

    遍历过程:利用链表,找孩子找兄弟找父亲;


    下载网 » Fiber架构

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元