Fiber应用

199 阅读7分钟

React Fiber是对核心算法的一次重新实现.

1为什么使用fiber

同步更新过程的局限,在现有React中,更新过程是同步的,这可能会导致性能问题。

  • 调用各个组件的生命周期函数
  • 计算和比对Virtual DOM
  • 最后更新DOM树

这整个过程是同步进行的,也就是说只要一个加载或者更新过程开始,那React就以不破楼兰终不还的气概,一鼓作气运行到底,中途绝不停歇。

表面上看,这样的设计也是挺合理的,因为更新过程不会有任何I/O操作嘛,完全是CPU计算,所以无需异步操作,的确只要一路狂奔就行了,但是,当组件树比较庞大的时候,问题就来了。

假如更新一个组件需要1毫秒,如果有200个组件要更新,那就需要200毫秒,在这200毫秒的更新过程中,浏览器那个唯一的主线程都在专心运行更新操作,无暇去做任何其他的事情。想象一下,在这200毫秒内,用户往一个input元素中输入点什么,敲击键盘也不会获得响应,因为渲染输入按键结果也是浏览器主线程的工作,但是浏览器主线程被React占着呢,抽不出空,最后的结果就是用户敲了按键看不到反应,等React更新过程结束之后,咔咔咔那些按键一下子出现在input元素里了。

这就是所谓的界面卡顿,很不好的用户体验。

Fiber机制

当组件树很大的时候就会出现这种问题,因为更新过程是同步地一层组件套一层组件,逐渐深入的过程,在更新完所有组件之前不停止,函数的调用栈就像下图这样,调用得很深,而且很长时间不会返回

栈协调器的执行过程
栈协调器的执行过程

因为JavaScript单线程的特点,每个同步任务不能耗时太长,不然就会让程序不会对其他输入作出相应,React的更新过程就是犯了这个禁忌,而React Fiber就是要改变现状。

为了解决这样的问题,从React16开始使用新的协调器(Fiber Recondiler),它是一种能够彻底解决主线程长时间占用问题的机制。

Fiber协调器的基本思想就是把整个差异比较、渲染和更新的过程拆成小任务,拆分的粒度成为Fiber,每一个Fiber对应一个虚拟dom节点。通过合理的调度机制来达到更强的控制力。简单的来说,每一个小任务完成后确认是否还有时间继续进行下一个任务,若有的话则继续进行,若时间不足则将下一个任务挂起,主线程不忙的时候,再继续进行。如图:

Fiber协调器的执行过程
Fiber协调器的执行过程

window.requestIdleCallback

window.requestIdleCallback()⽅方法将在浏览器器的空闲时段内调⽤用的函数排队。这使开发者能够在主事件循环上执⾏行行后台和低优先级⼯工作,⽽而不不会影响延迟关键事件,如动画和输⼊入响应。函数⼀一般会按先进先调⽤用的顺序执⾏行行,然⽽而,如果回调函数指定了了执⾏行行超时时间timeout,则有可能为了了在超时前执⾏行行函数⽽而打乱执⾏行行顺序。你可以在空闲回调函数中调⽤用requestIdleCallback(),以便便在下⼀一次通过事件循环之前调度另⼀一个回调

callback⼀一个在事件循环空闲时即将被调⽤用的函数的引⽤用。函数会接收到⼀一个名为IdleDeadline的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执⾏行行的状态

import {PLACEMENT} from "./CONST";
let nextUnitOfWork = null
// work in process 工作中的fiberRoot
let wipRoot = null
// 现在的根节点
let currentRoot = null
// 初始化
function render(vnode, container) {
  wipRoot = {
    node: container,
    props: {children: [vnode]},
    base: currentRoot
  }
  nextUnitOfWork = wipRoot
}

// 根据vnode,创建一个node
function createNode(vnode) {
  const {type, props} = vnode
  let node;
  if (type === 'TEXT') {
    node = document.createTextNode("")
  }else if(type){
    node = document.createElement(type)
  }
  updateNode(node, props)
  return node
}

// 构建fiber结构,遍历workInProgressFiber的子节点
function reconcilerChildren(workInProgressFiber, children) {
// 构建fiber结构
// 数组
// 数组 删除 新增
let prevSibling = null
let oldFiber = workInProgressFiber.base && workInProgressFiber.base.child
  for(let i = 0; i < children.length; i++) {
    const child = children[i]
    let newFiber = null
    // todo 比较type key
    newFiber = {
      type:  child.type,// 类型 区分不同的fiber.比如function, class host
      props: child.props,
      node: null,// 真实dom节点
      base:  null,// 存储fiber,便于比较
      parent: workInProgressFiber,
      effectTag: PLACEMENT
    }

    
    if (oldFiber) {
      oldFiber = oldFiber.sibling
    }
    // parent
    // child
    if (i === 0) {
      workInProgressFiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }
    // sibling
    prevSibling = newFiber
  }
}

// 更新节点上属性,如className、nodeValue等
function updateNode(node, nextVal) {
  Object.keys(nextVal)
    .filter(k => k !== "children")
    .forEach(k => {
      if (k.slice(02) === "on") {
        // 以on开头,就认为是一个事件,源码处理复杂一些,
        let eventName = k.slice(2).toLocaleLowerCase();
        node.addEventListener(eventName, nextVal[k]);
      } else {
        node[k] = nextVal[k];
      }
    });
}

// function组件,构建fiber
function updateFunctionComponent(fiber) {
  const {type, props} = fiber;
  const children = [type(props)];
  reconcilerChildren(fiber, children);
}

// 更新class组件,构建fiber
function updateClassComponent(fiber) {
  const {type, props} = fiber;
  const cmp = new type(props);
  const children = [cmp.render()];
  reconcilerChildren(fiber, children);
}

// 原生标签,,构建fiber
function updateHostComponent(fiber) {
  if (!fiber.node) {
    fiber.node = createNode(fiber)
  }
  const { children } = fiber.props
  reconcilerChildren(fiber, children)
  console.log('fiber', fiber)
}

// 执行当前任务,指定下一个任务,具体逻辑看下面实现及注释
function performUnitOfWork(fiber) {
  const {type} = fiber;
  if (typeof type === "function") {
    type.isReactComponent ? updateClassComponent(fiber) : updateFunctionComponent(fiber);
  } else {
    // 执行当前子任务
    updateHostComponent(fiber)
  }
  // 返回下一个子任务
  // 要下个任务的原则, 先找child
  if(fiber.child) {
    return fiber.child
  }
  // 没有子元素,找兄弟节点
  let nextFiber = fiber
  while(nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

// 看函数里具体注释
function workLoop(deadline) {
  // 下个子任务
  while(nextUnitOfWork && deadline.timeRemaining() > 1) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
  // todo
  // 没有子任务时,要提交
  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

// 提交
function commitRoot() {
  commitWorker(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
}

// 提交具体的fiber执行
function commitWorker(fiber) {
  if(!fiber) return
  // 向上查找
  let parentNodeFiber = fiber.parent
  while(!parentNodeFiber.node) {
    parentNodeFiber = parentNodeFiber.parent
  }
  const parentNode = parentNodeFiber.node
  // 更新 删除 新增
  if (fiber.effectTag === PLACEMENT && fiber.node !== null) {
    parentNode.appendChild(fiber.node)
  }
  commitWorker(fiber.child)
  commitWorker(fiber.sibling)
}

export default { render }

在CONST.js中的

export const PLACEMENT = "PLACEMENT";
export const UPDATE = "UPDATE";
export const DELETIONS = "DELETIONS";