react-fiber

370 阅读1分钟

1. 为什么需要fiber?

  • 对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局,动画等周期性任务无法立即得到处理,造成视觉上的卡顿,影响用户体验。
  • fiber进行任务的分解,解决了上面的问题。
  • 然后增量渲染(把渲染任务拆分成块,匀到多帧)。
  • 更新时能够暂停,终止,复用渲染任务。
  • 给不同类型的更新赋予优先级(onClick,onChange等在react中属于合成事件,react源码中会对这些事件赋予优先级)。
  • 并发方面新的基础能力。
  • 更流畅。
// 不用fiber的时候,处理childre,数组遍历的缺点就是完全跟顺序相关,必须要前面一个执行完,才会接着执行嘛。就阻塞了主进程,后面如果有优先级更高的就只能等着呀!!!
function reconcileChildren_old(children, node) {
  for (let i = 0; i < children.length; i++) {
    let child = children[i];
    if (Array.isArray(child)) {
      for (let j = 0; j < child.length; j++) {
        render(child[j], node);
      }
    } else {
      render(child, node);
    }
  }
}

2. 什么是fiber?

fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。

3. fiber相关知识点

  1. react底层有一套基于window.requestIdleCallback()的fiber实现机制,也就是schedule(react中requestIdleCallback的hack在 react/packages/scheduler/src/forks/SchedulerHostConfig.default.js),这个后面再说啦。
  2. window.requestIdleCallback(callback,options(可选参数))方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主 事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先 进先调用的顺序执行,然而,如果回调函数指定了执行超时时间 timeout ,则有可能为了在超时前执行 函数而打乱执行顺序。你可以在空闲回调函数中调用 requestIdleCallback() ,以便在下一次通过事件循环之前调度另一个回调。

4. 实现fiber

  1. Fiber 是 React 16 中新的协调引擎。它的主要目的是使 Virtual DOM 可以进行增量式渲染。 一个更新过程可能被打断。
  2. 所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段 Reconciliation Phase和第二阶段Commit Phase。
  3. fiber是链表的结构进行存储的。(注意,子的return一定是父fiber,但是父的child不一定是这个子fiber,因为这个子fiber不一定是父的第一个firber呀!!!)
  4. 实现代码 定义类型 --- ./const
export const TEXT = "TEXT";
export const PLACEMENT = "PLACEMENT";
export const UPDATE = "UPDATE";
export const DELETION = "DELETION";

fiber实现新增---./react-dom.js

import {TEXT, PLACEMENT} from "./const";

// 下一个单元任务  fiber
let nextUnitOfWork = null;
// work in progress fiber root (正在执行的根fiber)
let wipRoot = null;

/**
 * fiber架构
 * type: 标记类型
 * key: 标记当前层级下的唯一性
 * child : 第一个子元素 fiber
 * sibling : 下一个兄弟元素 fiber
 * return: 父fiber
 * node: 真实dom节点
 * props:属性值
 * base: 上次的节点 fiber
 * effectTag: 标记要执行的操作类型(删除、插入、更新)
 */

// ! vnode  虚拟dom对象
// ! node  真实dom

function render(vnode, container) {
  // 初始wipRoot的值
  wipRoot = {
    node: container,
    props: {
      children: [vnode]
    }
  };
  nextUnitOfWork = wipRoot;
}

// 创建node
function createNode(vnode) {
  const {type, props} = vnode;
  let node = null;
  // 判断节点类型
  if (type === TEXT) {
    node = document.createTextNode("");
  } else if (typeof type === "string") {
    node = document.createElement(type);
  } else if (typeof type === "function") {
    // 判断是函数组件还是类组件
    node = type.prototype.isReactComponent
      ? updateClassComponent(vnode)
      : updateFunctionComponent(vnode);
  } else {
    node = document.createDocumentFragment();
  }

  // 把props.children遍历,转成真实dom节点 ,再插入node
  // reconcileChildren(props.children, node);
  // 更新属性节点
  updateNode(node, props);
  return node;
}

// 类组件
function updateClassComponent(fiber) {
  const {type, props} = fiber;
  const cmp = new type(props);
  let children = [cmp.render()];
  reconcileChildren(fiber, children);
}

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

// 更新属性值,如className、nodeValue等
function updateNode(node, nextVal) {
  Object.keys(nextVal)
    .filter(k => k !== "children")
    .forEach(k => {
      node[k] = nextVal[k];
    });
}

// workInProgressFiber Fiber ->child->sibling
// children 数组
function reconcileChildren(workInProgressFiber, children) {
  // 构建fiber架构
  let prevSlibling = null;
  for (let i = 0; i < children.length; i++) {
    let child = children[i];
    // 现在只考虑初次渲染
    // 创建一个新的fiber
    let newFiber = {
      type: child.type,
      props: child.props,
      node: null,
      base: null,
      return: workInProgressFiber,
      effectTag: PLACEMENT
    };
    // 形成一个链表结构
    if (i === 0) {
      workInProgressFiber.child = newFiber;
    } else {
      prevSlibling.sibling = newFiber;
    }
    prevSlibling = newFiber;
  }
}

function updateHostComponent(fiber) {
  if (!fiber.node) {
    fiber.node = createNode(fiber);
  }
  // 协调子元素
  const {children} = fiber.props;
  reconcileChildren(fiber, children);
}

function performUnitOfWork(fiber) {
  //执行当前任务 更新当前fiber节点
  const {type} = fiber;
  if (typeof type === "function") {
    // class function
    type.prototype.isReactComponent
      ? updateClassComponent(fiber)
      : updateFunctionComponent(fiber);
  } else {
    // 原生标签
    updateHostComponent(fiber);
  }

  //获取下一个子任务(fiber)
  if (fiber.child) {
    return fiber.child;
  }

  let nextFiber = fiber;
  while (nextFiber) {
    // 找到兄弟
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    // 没有兄弟 往祖先上找
    nextFiber = nextFiber.return;
  }
}

function workLoop(deadline) {
  // 有下一个任务 并且当前帧没有结束
  // 这里的时间是模拟,源码当中用的过期时间,源码中的过期时间和时间单位相关内
  while (nextUnitOfWork && deadline.timeRemaining() > 1) {
    //执行当前任务
    //获取下一个子任务(fiber)
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  if (!nextUnitOfWork && wipRoot) {
    // 提交
    commitRoot();
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

// ! 提交任务
function commitRoot() {
  commitWorker(wipRoot.child);
  wipRoot = null;
}

function commitWorker(fiber) {
  if (!fiber) {
    return;
  }

  // 找到parentNode,
  // 找到最近的有node节点的祖先fiber
  let parentNodeFiber = fiber.return;
  while (!parentNodeFiber.node) {
    parentNodeFiber = parentNodeFiber.return;
  }

  const parentNode = parentNodeFiber.node;
  if (fiber.effectTag === PLACEMENT && fiber.node !== null) {
    // 新增插入(dom父子关系插入)
    parentNode.appendChild(fiber.node);
  }

  commitWorker(fiber.child);
  commitWorker(fiber.sibling);
}

export default {render};