美文网首页
react fiber

react fiber

作者: 一土二月鸟 | 来源:发表于2020-08-30 16:45 被阅读0次

    一帧的生命周期

    • 浏览器动画的执行频率一般为每秒60次。相当于每秒60帧。
    • 一帧大概为16ms(1000ms/60帧),不同的电脑配置可能会有所差别


    requestIdleCallback

    • requestIdleCallback 为fiber的核心,它是浏览器自带的方法。可用于在浏览器每一帧的空闲时段处理回调任务(空闲时间即为上图的最后一个阶段)。单个回调任务最好能在此帧的剩余时间内完成,否则会引起卡顿。
    • 这样做的好处是避免占用主线程长时间执行一个大的任务,导致浏览器卡顿。
        function sleep(time) {
          let startTime = Date.now();
          while (Date.now() - startTime < time) {
            continue;
          }
        }
    
        const fnArr = [
          function () {
            sleep(20);
            console.log('任务1')
          },
          function () {
            sleep(20);
            console.log('任务2')
          },
          function () {
            console.log('任务3')
          }
        ];
    
    
        function rIdCb (deadline) {
          console.log('此帧剩余时间' + deadline.timeRemaining());
          // timeRemaining 代表剩余时间; didTimeout代表是否已超时;
          while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && fnArr.length > 0) {
            fnArr.shift()();
          }
          if(fnArr.length > 0){
           requestIdleCallback(rIdCb, {timeout: 1000}); // timeout代表执行rIdCb的超时时间,如果超过该时间仍未执行,didTimeout则为true;
          }
        }
    
        requestIdleCallback(rIdCb, { timeout: 1000 });
    

    requestAnimationFrame

    • 每次执行完优先级高的input click事件、timeout、浏览器窗口等事件,会在绘制页面前执行requestAnimationFrame的回调方法。
    • 由于每一帧的时间周期为16ms左右,因此以下demo打印的时间差为16ms左右,同时可实现动画平滑的效果。
    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8" />
      <title>test</title>
      <style>
        .box {
          width: 0px;
          height: 20px;
          background: lightgray;
        }
      </style>
    </head>
    
    <body>
      <div class="box"></div>
      <button id="btn">change width</button>
      <script type="text/javascript">
    
        const obtn = document.getElementById('btn');
        const odiv = document.getElementsByClassName('box')[0];
        let start = 0;
        obtn.addEventListener('click', () => {
          odiv.style.width = 0;
          start = Date.now();
          requestAnimationFrame(rafCb);
        })
    
        const rafCb = () => {
          let currTime = Date.now();
          console.log(currTime - start);  // 此时间差为每一帧的执行周期,为16ms左右
          start = currTime;
          odiv.style.width = odiv.offsetWidth + 1 + 'px'; 
          odiv.innerHTML = odiv.offsetWidth + '%';
    
          if (odiv.offsetWidth < 100) {
            requestAnimationFrame(rafCb); // 利用raf代替常规的while循环或递归,可实现16ms执行一次rafCb,实现了既不占用主线程,动画又平滑的效果
          }
        }
    
      </script>
    </body>
    
    </html>
    

    单链表

    // 定义第一份数据
    let data1 = { x: 1 };
    // 将第一份数据data1的内存地址指向火车头和最后一节车厢
    let huochetou = lastone = data1;
    // 定义第二份数据
    let data2 = { y: 2 };
    // 将第二份数据data2的内存地址指向最后一节车厢的next属性。(与此同时,火车头也拥有的next属性,其值为data2);
    lastone.next = data2;
    // 将第二份数据data2的内存地址指向最后一节车厢lastone。(此时,火车头的next属性和lastone指向的都是data2的内存地址);
    lastone = data2;
    // 定义第三份数据
    let data3 = { z: 3 };
    // 将第三份数据data3的内存地址指向lastone的next属性。(此时火车头的next将新增next属性,并指向data3)
    lastone.next = data3;
    // 将lastone指向data3
    lastone = data3;
    
    最终在火车头上通过next实现了单链表效果
    
    • 利用单链表实现react的forceUpdate
    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
      <meta charset="UTF-8" />
      <title>test</title>
    </head>
    
    <body>
      <div class="box"></div>
      <button id="btn">change width</button>
      <script type="text/javascript">
    
        class Update {
          constructor(payload, nextUpdate) {
            this.payload = payload;
            this.nextUpdate = nextUpdate;
          }
        }
    
        class UpdateQueue {
          constructor() {
            this.baseState = null;
            this.firstUpdate = null;
            this.lastUpdate = null;
          }
          enqueueUpdate(update) {
            if (this.firstUpdate === null) {
              this.lastUpdate = update;
              this.firstUpdate = this.lastUpdate;
            } else {
              this.lastUpdate.nextUpdate = update;
              this.lastUpdate = update;
            }
          }
    
          forceUpdate() {
            let currentState = this.baseState || {}; // 初始状态
            let currentUpdate = this.firstUpdate;
            while (currentUpdate) {
              let nextState = typeof currentUpdate.payload === 'function'
                ? currentUpdate.payload(currentState)
                : currentUpdate.payload;
              currentState = { ...currentState, ...nextState };
              currentUpdate = currentUpdate.nextUpdate;
            }
            this.firstUpdate = this.lastUpdate = null;
            this.baseState = currentState;
            return currentState;
          }
        }
    
        let queue = new UpdateQueue();
        queue.enqueueUpdate(new Update({ name: 'skyler' }));
        queue.enqueueUpdate(new Update({ number: 0 }));
        queue.enqueueUpdate(new Update(state => ({ number: state.number + 1 })));
        queue.enqueueUpdate(new Update(state => ({ number: state.number + 1 })));
        console.log(queue.firstUpdate, queue.lastUpdate);
        queue.forceUpdate();
        console.log(queue.baseState);
      </script>
    </body>
    
    </html>
    

    react的链表结构及遍历规则

    • 数据结构
    const A1 = { type: 'div', key: 'A1' };
    const B1 = { type: 'div', key: 'B1', return: A1 };
    const B2 = { type: 'div', key: 'B2', return: A1 };
    const C1 = { type: 'div', key: 'C1', return: B1 };
    const C2 = { type: 'div', key: 'C2', return: B1 };
    
    A1.child = B1;
    B1.child = C1;
    B1.sibling = B2;
    C1.sibling = C2;
    
    • 遍历规则 绿色会遍历顺序,蓝色为完成顺序


      image.png
    • 代码实现
    //  fiber 数据结构
    const A1 = { type: 'div', key: 'A1' };
    const B1 = { type: 'div', key: 'B1', return: A1 };
    const B2 = { type: 'div', key: 'B2', return: A1 };
    const C1 = { type: 'div', key: 'C1', return: B1 };
    const C2 = { type: 'div', key: 'C2', return: B1 };
    
    // 每个节点只有自己的父亲、大儿子、弟弟
    A1.child = B1; // 每个父亲只携带一个大儿子,让大儿子携带大儿子自己的弟弟,以此建立链条关系
    B1.child = C1;
    B1.sibling = B2;
    C1.sibling = C2;
    
    
    /**
     * 1. 开始从顶点遍历。
     * 2. 如果有大儿子,先遍历大儿子。(同时要等自己所有的儿子任务完成自己的任务才算完成。)
     * 3. 如果没有大儿子,说明自己的任务已完成。开始找自己的弟弟。
     * 4. 如果找到弟弟,开始遍历弟弟。
     * 5. 没有弟弟,则通过自己的父亲找父亲的弟弟,同时代表自己的父亲已完成全部工作
     * 6. 父亲的弟弟开始循环上面的步骤,直到根节点完成任务
     */
    
    let rootFiber = A1;  // A1为根节点 通知携带了自己的所有的children
    
    // 1. 开始从顶点遍历
    workLoop(rootFilber); 
    
    // nextUnitOfWork为下一个执行单元(链表的单个节点,也叫做一个fiber)
    function workLoop(nextUnitOfWork) {
      while (nextUnitOfWork) { // 如果有执行单元就执行,然后返回下一个执行执行单元
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    
      console.log('render阶段结束')
    }
    
    // 处理一个fiber
    function performUnitOfWork(fiber) {
      beginWork(fiber); // 此fiber开始工作
      if (fiber.child) { // 2. 如果有大儿子先遍历大儿子
        return fiber.child;
      } // 3. 如果没有大儿子,说明自己的任务已完成。开始找自己的弟弟。
    
      while (fiber) {
        completeUnitOfWork(fiber); 
        if (fiber.sibling) { // 4. 如果找到弟弟,开始遍历弟弟。
          return fiber.sibling;
        } // 5. 没有弟弟,则通过自己的父亲找父亲的弟弟,同时代表自己的父亲已完成全部工作
        fiber = fiber.return; 
      }
    
      return null; // 6. 代表任务执行到了rootFiber的父亲,它的父亲为null,所以本次任务结束运行
    
    }
    
    function completeUnitOfWork(fiber) {
      console.log(fiber.key, '执行完成');
    }
    
    function beginWork(fiber) {
      console.log(fiber.key, '开始执行'); // A1 B1 C1
    }
    

    requestIdleCallback和链表遍历相结合

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <script>
        //  fiber 数据结构
        const A1 = { type: 'div', key: 'A1' };
        const B1 = { type: 'div', key: 'B1', return: A1 };
        const B2 = { type: 'div', key: 'B2', return: A1 };
        const C1 = { type: 'div', key: 'C1', return: B1 };
        const C2 = { type: 'div', key: 'C2', return: B1 };
    
        // 每个节点只有自己的父亲、大儿子、弟弟
        A1.child = B1; // 每个父亲只携带一个大儿子,让大儿子携带大儿子自己的弟弟,以此建立链条关系
        B1.child = C1;
        B1.sibling = B2;
        C1.sibling = C2;
    
    
        /**
         * 1. 开始从顶点遍历。
         * 2. 如果有大儿子,先遍历大儿子。(同时要等自己所有的儿子任务完成自己的任务才算完成。)
         * 3. 如果没有大儿子,说明自己的任务已完成。开始找自己的弟弟。
         * 4. 如果找到弟弟,开始遍历弟弟。
         * 5. 没有弟弟,则通过自己的父亲找父亲的弟弟,同时代表自己的父亲已完成全部工作
         * 6. 父亲的弟弟开始循环上面的步骤,直到根节点完成任务
         */
    
        let nextUnitOfWork = A1;
    
        let startTime = Date.now();
    
        // 1. 开始从顶点遍历
        requestIdleCallback(workLoop, { timeout: 1000 });
    
        // nextUnitOfWork为下一个执行单元(链表的单个节点,也叫做一个fiber)
        function workLoop(deadline) {
          while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextUnitOfWork) { // 如果有执行单元并且本帧还有剩余时间就执行,然后返回下一个执行执行单元
            nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
          }
          if (!nextUnitOfWork) {
            console.log('render阶段结束。共耗时:', Date.now() - startTime);
          } else {
            requestIdleCallback(workLoop, { timeout: 1000 })
          }
        }
    
        // 处理一个fiber
        function performUnitOfWork(fiber) {
          beginWork(fiber); // 此fiber开始工作
          if (fiber.child) { // 2. 如果有大儿子先遍历大儿子
            return fiber.child;
          } // 3. 如果没有大儿子,说明自己的任务已完成。开始找自己的弟弟。
    
          while (fiber) {
            completeUnitOfWork(fiber);
            if (fiber.sibling) { // 4. 如果找到弟弟,开始遍历弟弟。
              return fiber.sibling;
            } // 5. 没有弟弟,则通过自己的父亲找父亲的弟弟,同时代表自己的父亲已完成全部工作
            fiber = fiber.return;
          }
    
          return null; // 6. 代表任务执行到了rootFiber的父亲,它的父亲为null,所以本次任务结束运行
    
        }
    
        function completeUnitOfWork(fiber) {
          sleep(20);
          console.log(fiber.key, '执行完成');
        }
    
        function beginWork(fiber) {
          console.log(fiber.key, '开始执行'); // A1 B1 C1
        }
    
        function sleep(time) {
          let start = Date.now();
          while (Date.now() - start < time) { }
        }
      </script>
    </body>
    
    </html>
    

    相关文章

      网友评论

          本文标题:react fiber

          本文链接:https://www.haomeiwen.com/subject/gdeesktx.html