2021-03-03 React原理解析之fiber

201 阅读4分钟

协调-reconciliation

设计动力

调用React的render()方法会创建一颗由React元素组成的树,在下一次state、props或context更新时候,相同的render()方法会返回一颗不同的树。React需要基于这两颗树之间的差别来判断如何有效的更新UI以保证当前UI与最新的树保持同步

React提出了一套O(n)-n是树中元素的数量,启发式算法(diff算法):

1、两个不同类型的元素会产生出不同的树

2、开发者可以通过keyprop属性来暗示哪些子元素在不同的渲染下能保持稳定

diff算法-算法复杂度O(n)

diff策略

1、同级比较,Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计

2、拥有不同类型的两个组件将会生成不同的树形结构,例如:div->p,CompA->CompB

3、开发者可以通过keyprop属性来暗示哪些子元素在不同的渲染下能保持稳定

节点能够复用的三个条件:同级节点、key值相同、节点类型相同

diff过程

对比两个虚拟dom时会有三种操作:删除、替换和更新

注:vnode是现在的虚拟dom,newVnode是新虚拟dom

删除:newVnode不存在时

替换:vnode和newVnode类型不同或key不同时

更新:有相同类型和key时,但vnode和newVnode的属性不同时

fiber

为什么需要fiber

  1. 对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验
  2. 任务分解就是解决上面的问题
  3. 增量渲染(把渲染任务拆分成块,匀到多帧)
  4. 更新时能够暂停,终止,复用渲染任务
  5. 给不同的类型的更新赋予优先级
  6. 并发方面的基础能力
  7. 更流畅

什么是fiber

fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个fiber,它的数据结构类似于链表,有指针指向,表示vnode节点 fiber

实现fiber

window.requestIdleCallback(callback[, options]) 此方法将在浏览器的空闲时段内调用函数队列。这使得开发者能够在事件循环上执行后台和优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱顺序

可以在空闲回调函数中调用requestIdleCallback(),以便在下一次通过事件循环之前调度另一个回调

Fiber是React 16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量渲染 一个更新过程可能会被打断,所以React Fiber一个更新过程被分为两个阶段:

第一个阶段Reconciliation Phase

第二个阶段Commit Phase

有fiber的react-dom.js文件

/**
 * vnode 虚拟DOM
 * node 真实DOM
 */

// 根节点 fiber
let wipRoot = null;
function render(vnode, container) {
  /* console.log("vnode", vnode);
  // vnode->node
  const node = createNode(vnode);
  // node 插入到container中
  container.appendChild(node); */

  wipRoot = {
    type: "div",
    props: { children: { ...vnode } },
    stateNode: container
  };

  nextUnitWork = wipRoot;
}

function isString(str) {
  return typeof str === "string";
}

// 根据vnode,生成node
function createNode(workInProgress) {
  let node = document.createElement(workInProgress.type);
  updateNode(node, workInProgress.props);

  return node;
}

// 更新属性
function updateNode(node, nextVal) {
  Object.keys(nextVal)
    // .filter(k => k !== "children")
    .forEach(k => {
      if (k === "children") {
        if (isString(nextVal.children)) {
          node.textContent = nextVal.children;
        }
      } else {
        if (k === "style") {
          for (let attr in nextVal.style) {
            node.style[attr] = nextVal.style[attr];
          }
        } else {
          node[k] = nextVal[k];
        }
      }
    });
}

// 原生标签
function updateHostComponent(workInProgress) {
  // 修身
  // 构建真实dom节点
  if (!workInProgress.stateNode) {
    workInProgress.stateNode = createNode(workInProgress);
  }

  // 齐家
  // 协调子节点
  reconcileChildren(workInProgress, workInProgress.props.children);
  console.log("workInProgress", workInProgress);
}

// 文本
function updateTextComponent(workInProgress) {
  if (!workInProgress.stateNode) {
    workInProgress.stateNode = document.createTextNode(workInProgress.props);
  }
}

// 函数组件
function updateFunctionComponent(workInProgress) {
  const { type, props } = workInProgress;
  const children = type(props);
  reconcileChildren(workInProgress, children);
}

// 类组件
function updateClassComponent(workInProgress) {
  const { type, props } = workInProgress;
  // 先实例化
  const instance = new type(props);
  const children = instance.render();
  reconcileChildren(workInProgress, children);
}

// <>Fragment节点
function updateFragmentComponent(workInProgress) {
  reconcileChildren(workInProgress, workInProgress.props.children);
}

// 最假的吧,最简单的也是协调
function reconcileChildren(workInProgress, children) {
  if (isString(children)) {
    return;
  }
  let newChildren = Array.isArray(children) ? children : [children];

  let previousNewFiber = null;
  for (let i = 0; i < newChildren.length; i++) {
    let child = newChildren[i];
    // 构建fiber节点
    let newFiber = {
      type: child.type, // 类型
      props: { ...child.props }, // 属性
      stateNode: null, // 如果是原生标签代表dom节点,如果是类组件就代表实例
      child: null, // 第一个子节点fiber
      sibling: null, // 下一个兄弟节点 fiber
      return: workInProgress // 父节点
    };

    if (isString(child)) {
      newFiber.props = child;
    }

    if (i === 0) {
      // 第一个子fiber
      workInProgress.child = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }

    previousNewFiber = newFiber;
  }
}

/**
 * fiber数据结构
 * type
 * props
 * key
 * stateNode 如果是原生标签代表dom节点,如果是类组件代表实例
 * child 第一个子节点
 * sibling 下一个兄弟节点
 * return 指向父节点
 */

// workInProgress当前正在进行的中的fiber
function performNextUnitWork(workInProgress) {
  // step1 执行当前任务
  const { type } = workInProgress;
  if (isString(type)) {
    // 原生标签
    updateHostComponent(workInProgress);
  } else if (typeof type === "function") {
    if (type.prototype.isReactComponent) {
      // 类组件
      updateClassComponent(workInProgress);
    } else {
      // 函数组件
      updateFunctionComponent(workInProgress);
    }
  } else if (typeof type === "undefined") {
    // 文本
    updateTextComponent(workInProgress);
  } else {
    // Fragment
    updateFragmentComponent(workInProgress);
  }

  // step2 并且返回下一个任务(深度优先遍历)
  if (workInProgress.child) {
    // 有孩子返回孩子
    return workInProgress.child;
  }

  let nextFiber = workInProgress;
  while (nextFiber) {
    // 有兄弟返回兄弟,没有返回叔叔
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    // 叔叔就是爸爸的兄弟
    nextFiber = nextFiber.return;
  }
}

// 下一个要执行的任务
let nextUnitWork = null; // fiber
function workLoop(IdleDeadline) {
  while (nextUnitWork && IdleDeadline.timeRemaining() > 1) {
    // 执行当前任务,并且返回下一个任务
    nextUnitWork = performNextUnitWork(nextUnitWork);
  }

  // 没有任务就提交
  if (!nextUnitWork && wipRoot) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

function commitRoot() {
  commitWorker(wipRoot.child);
  wipRoot = null;
}

function commitWorker(workInProgress) {
  if (!workInProgress) {
    return;
  }
  // 更新自己
  let parentNodeFiber = workInProgress.return;

  while (!parentNodeFiber.stateNode) {
    parentNodeFiber = parentNodeFiber.return;
  }

  let parentNode = parentNodeFiber.stateNode;
  if (workInProgress.stateNode) {
    parentNode.appendChild(workInProgress.stateNode);
  }

  // 更新子节点
  commitWorker(workInProgress.child);
  // 更新下一个兄弟节点
  commitWorker(workInProgress.sibling);
}

// 在浏览器的空闲时间段内调用的函数排队
requestIdleCallback(workLoop);

// eslint-disable-next-line
export default { render };