美文网首页
React深入5-源码1(React Fiber)

React深入5-源码1(React Fiber)

作者: 申_9a33 | 来源:发表于2022-02-27 18:48 被阅读0次

React 深入5(React Fiber)

  • 1.传统Dom渲染


    image.png
  • 2.reconciliaton协调
    • 2.1将vnode,通过reader()方法转换成node,通用算法复杂度为O(n3),其中n是树中元素的数量
    • React基于以下两个假设提出一套O(n)的启发式算法
      • 2.1.1. 两个类型不同的元素会产生不同的树
      • 2.1.2. 开发可以通过key prop来暗示那些子元素在不同的渲染情况下能保持稳定
    • 2.2diff过程同层比较,深度优先,对比两个虚拟dom时,会有三种操作:删除,替换和更新
      • 2.2.1.删除: newVnode不存在时
      • 2.2.2.替换: vnode和newVnode类型不同或key不同时
      • 2.2.3.更新: 有相同类型和key,但vnode和newVnode不同时
  • 3.Fiber协调 是React16中新的协调引擎,主要目的是使Virtual Dom能够进行增量渲染
    • 3.1 对于大型项目,组件树很大,递归遍历的成本就会很大,造成主进程一直被占用,无法立即处理布局,动画等周期性任务,造成视觉上的卡顿,fiber用于处理以上问题
    • 3.2 增量渲染(把渲染任务拆分成块,匀到多帧)
    • 3.4 更新时能够暂停,终止,复用渲染任务
    • 3.5 给不同类型的更新赋予优先级

实现fiber

window.requestIdleCallback(callback[,options])

  • callback 一个在事件循环空闲时即将被调用的函数的引用。函数会接受一个名为IdleDeadline,可以当前空闲时间以及回调是否在超时时间前已经执行的状态
  • options(可选) timout属性

React Fiber 一个更新过程被分为两个阶段(Phase)

  • 第一个阶段 Reconciliaton Phase,将虚拟DOM转换成fiber结构,并形成链表
  • 第二阶段 Commit Phase, 执行挂载

fiber 结构

type: 标记节点类型
key: 标记节点在当前层级下的唯一性
props: 属性
index: 标记当前层级下的位置
child: 第一个子节点
sibling: 下一个兄弟节点
return: 父节点
stateNode: 如果组件是原生标签则是dom节点,如果是类组件是类实例

1. 修改render()函数

  • 构建一个 fiber
  • 使用 scheduleUpdateOnFiber() 来更新和渲染这个fiber
/**
 * 渲染vnode到container中
 * */
export function render(vnode:any, container:HTMLElement) {
  const FiberRoot = {
    type: container?.nodeName.toLocaleLowerCase(),
    stateNode: container,
    props: { children: vnode },
  };
  scheduleUpdateOnFiber(FiberRoot);
}

2. 实现scheduleUpdateOnFiber()函数

  • 设置 根wipRoot 为传入的FiberRoot,同时清空 wipRoot的sibling
  • 将下一个要执行的 nextUnitOfwork 设置为传入的FiberRoot
// src\react\ReactFiberWorkLoop.ts
let wipRoot:IFiber | null = null;
let nextUnitOfwork:IFiber | null = null;
export function scheduleUpdateOnFiber(fiber:IFiber) {
  wipRoot = fiber;
  wipRoot.sibling = null;
  nextUnitOfwork = wipRoot;
}

3.实现workLoop(IdleDeadline), 在浏览器空闲时调用,ReconciliatonCommit

  • Reconciliaton Phase ,利用浏览器空闲时间遍历wip,转换为fiber,形成链表
function workLoop(IdleDeadline:IdleDeadline) {
  while(nextUnitOfwork && IdleDeadline.timeRemaining()>=0){
    // 第一个阶段 Reconciliaton Phase
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }

  if(!nextUnitOfWork && wipRoot){
    // 第二阶段 Commit Phase
    commitRoot()
  }
}
window.requestIdleCallback(workLoop);

4. 实现performUnitOfWork(nextUnitOfWork),Reconciliaton阶段生成 fiber

  • 根据wip的type,去执行对应的update(与React jsx相似),大概是
    • 1.原生节点,直接document.createElement创建对应的node
    • 2.函数组件,执行对应方法
    • 3.类组件,实例对应的类
    • 4.然后与所有的children,相互链接,形成双向链表,相邻fiber,形成单向链表
  • 采用深度优先的模式,返回下一个fiber
// src\react\ReactFiberWorkLoop.ts
function performUnitOfWork(wip:IFiber):IFiber|null {
  const { type } = wip;
  if (isFunction(type)) {
    type.prototype.isReactComponent
      ? updateClassComponent(wip)
      : updateFunctionComponent(wip);
  }else(isString(type)){
    updataHostComponent(wip)
  }else{
    updateFragementComponent(wip)
  }

  // 2.返回下一个wip,深度优先
  if (wip.child) {
    return wip.child;
  }

  let next:IFiber | undefined = wip;
  while (next) {
    if (next.sibling) {
      return next.sibling;
    }
    next = wip.child;
  }

  return null
}

5.实现commitRoot() ,Commit阶段将 stateNode连接在一起

  • 因为Reconciliaton已经生成的所有的stateNode,并且fiberreturn指向父fiber
  • 所以只要拿到parentFiber的stateNode,appendChild当前fiber.stateNode即可连接成一整个HTML
  • 函数组件与类组件也是一个fiber,但是不存在stateNode
 function commitRoot(){
  commitWorker(wipRoot?.child);
}

function commitWorker(wip){
  if(!wip){
    return
  }

  // 将子节点挂载在父节点上
  const {stateNode} = wip
  const parentNode = getParentNode(wip)
  if(stateNode){
    parentNode.appendChild(stateNode)
  }

  // 递归
  commitWorker(wip.child)
  commitWorker(wip.sibling)
}

function getParentNode(fiber){
  let nuxt = fiber
  while(nuxt){
    if(nuxt.return?.stateNode){
      return nuxt.return?.stateNode
    }
    nuxt = nuxt.return
  }

  throw new Error('getParentNode is null')
}

实现 Reconciler 的 update

  • 在fiber中,Reconciler 阶段会根据 将wip转换为fiber,并且形成链表
  • update 填充statenode
  • reconcileChildren 将wip转换为fiber,并且形成链表

1.原生标签updataHostComponent()

  • 直接创建
  • 更新属性
  • 将所有children转换fiber,并形成链表
export function updataHostComponent(wip:any) {
  if (!wip.stateNode) {
    wip.stateNode = document.createElement(wip.type);
    updateNode(wip.stateNode, wip.props);
  }

  reconcileChildren(wip, wip.props.children);
}

2. 类组件updateClassComponent()

  • 实例化,并执行内部的render,拿到children
  • 将所有children转换fiber,并形成链表
export function updateClassComponent(wip:any) {
  const { type, props } = wip;
  // eslint-disable-next-line new-cap
  const instance = new type(props);
  const children = instance.render();

  reconcileChildren(wip, children);
}

3. 函数组件updateFunctionComponent()

  • 执行函数拿到children
  • 将所有children转换fiber,并形成链表
export function updateFunctionComponent(wip:any) {
  const { type, props } = wip;
  const children = type(props);

  reconcileChildren(wip, children);
}

4. 空节点 updateFragementComponent()

  • 直接拿到拿到children
  • 将所有children转换fiber,并形成链表
export function updateFragementComponent(wip:any) {
  reconcileChildren(wip, wip.props.children);
}

5.reconcileChildren()

  • 此方法入参是parentFiber,和children(虚拟Dom)
  • 首先将虚拟Dom转换为fiber
  • 然后根据规则填充 return,child,sibling
    • 规则1:所有子fiberreturn指向parentFiber,
    • 规则2:parentFiberchild指向第一个fiber
    • 规则3:上一个fibersibling指向下一个fibling
function reconcileChildren(returnFiber:any, children:any) {
  if (isString(children)) {
    return;
  }

  const newChildren = Array.isArray(children) ? children : [children];
  let previousNewFiber:any | null = null;
  // eslint-disable-next-line no-plusplus
  for (let i = 0; i < newChildren.length; i++) {
    const child = newChildren[i];
    const newFiber = createFiber(child, returnFiber);

    if (previousNewFiber === null) {
      // parentFiber的child指向第一个fiber
      returnFiber.child = newFiber;
    } else {
      // 否则将上一个fiber,指向当前fiber
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
  }
}

源码

相关文章

网友评论

      本文标题:React深入5-源码1(React Fiber)

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