【React】React源码梳理笔记(十)

前言

  • 本篇继续梳理下fiber基础知识。

Fiber树

  • 从上一篇可以发现,解决浏览器卡顿其实是把一个大任务拆成多个小任务。而react里,diff操作可以算是一个非常大的任务,所以需要将这个大任务拆成多个小任务解决。
  • 那么如何拆呢?就是通过把一个递归操作,变成一个可中断操作。
  • 和上一篇原理一样,每次完成一个小任务,检查时间够不够执行下一个任务,不够的话将控制权交给浏览器,等待浏览器渲染完继续下一个任务。
  • fiber的结构很简单,指向父节点的return,指向孩子的child,指向兄弟的sibiling。但这个结构特殊在于父节点没法一次头拿齐所有的孩子,必须通过链表找到所有孩子。
  • 这里举个例子,看一下如何拆成多个小任务的:
  • 比如有这样一个结构关系,传统的递归是这样做:

在这里插入图片描述

  • 然后使用递归:
let C1 = { type: 'div', key: 'C1'};
let C2 = { type: 'div', key: 'C2'};
let B1 = { type: 'div', key: 'B1', child:[C1,C2] };
let B2 = { type: 'div', key: 'B2'};
let A1 = { type: 'div', key: 'A1', child:[B1,B2] };
function deep(root){
  console.log(root.key)
  if(root.child){
    root.child.forEach(it => {
      deep(it)
    }); 
  }
}
deep(A1)
  • 最后输出:
A1
B1
C1
C2
B2
  • 这个过程一气呵成无法中断。
  • 我们将其结构改为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
  • 使用同样进行递归操作:
let nextUnitOfwork=A1
function workLoop(){
  while (nextUnitOfwork){
    nextUnitOfwork=performUnitOfWork(nextUnitOfwork)
  }
}
function performUnitOfWork(fiber){
    console.log('start',fiber.key)
    if(fiber.child){
      return fiber.child
    }
    while(fiber){
      console.log('complete',fiber.key)
      if(fiber.sibling){
        return fiber.sibling
      }
      fiber=fiber.return
    }
}
workLoop()
  • 输出:
start A1
start B1
start C1
complete C1
start C2
complete C2
complete B1
start B2
complete B2
complete A1
  • 这里当fiber进入while,便会去找他的兄弟或者父亲,这个节点就算遍历完成状态。当然如果返回父亲,父亲也会在while循环里,所以不是找兄弟就是找爷爷。以此类推。
  • 这样就把一个传统递归变成了一个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

  let nextUnitOfwork = A1
  function workLoop(deadline) {
    //while (nextUnitOfwork){
    while ((deadline.timeRemaining() > 0 || deadline.timeout) && nextUnitOfwork) {
      nextUnitOfwork = performUnitOfWork(nextUnitOfwork)
    }
    if (!nextUnitOfwork) {
      console.log('over')
    } else {
      window.requestIdleCallback(workLoop, { timeout: 1000 })
    }
  }
  function performUnitOfWork(fiber) {
    console.log('start', fiber.key)
    if (fiber.child) {
      return fiber.child
    }
    while (fiber) {
      console.log('complete', fiber.key)
      if (fiber.sibling) {
        return fiber.sibling
      }
      fiber = fiber.return
    }
  }
  window.requestIdleCallback(workLoop, { timeout: 1000 })
  • 同步部分改个判断,当时间不够交给浏览器进行递归。
  • 当然目前这个任务耗时比较短,我们可以在performUnitOfWork里穿插别的逻辑比如上篇的sleep,让其在某个fiber中运行的耗时多一点,可以试一下。
    if (fiber.key === 'B1') {
        sleep(2000)
      }
  • 有人可能觉得,这效果用generator也能做啊,为啥这么搞?
  • 这个问题可以参考issue。里面说了generator存在2个问题,其中最大的问题就是没法重用已经算好的值。

副作用链表

  • fiber分为2个阶段,一个叫reconciliation阶段,一个叫commit阶段。而上面那段搞得就是reconciliation阶段,这段说的副作用链表也是reconciliation阶段产物。
  • 上面那段做的这个fiber树中,会在console complete那个地方进行副作用收集。
  • 收集的其实就是副作用,比如dom的挂载卸载更新之类。
  • 收集完就得到一个副作用链表。
  • 到这里reconciliation阶段就搞完了,reconciliation阶段是可以中断的,中断原理就是上面部分所讲。而commit阶段则是将这个副作用链表上所有操作搞一遍,期间不能中断,否则一个元素一个元素渲染不是看起来很怪异?特别需求另外说。
  • 链表就是有指针指向下一个节点的嘛,所以,这个副作用链表在递归过程中,通过fiber树结构中的指向,来获取上一个有副作用的节点,将其指向下一个有副作用的节点,从而完整的形成一个链表。
  • 基本基础就这些内容,剩下的下次写。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

业火之理

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值