从零实现一个mini-react(二)Fiber和并发模式

441 阅读3分钟

前因

上节的递归调用会出现一个问题,也就是一旦开始渲染,就不能停止了,直到渲染出完整的树结构。也就是说会造成主线程被持续占⽤,造成的后果就是主线程上的布局、动画等周期性任务就⽆法立即得到处理,造成视觉上的卡顿,影响⽤户体验。

在浏览器中,页面是一帧一帧绘制出来的,主流浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次,在这一帧中浏览器要完成JS脚本执行、样式布局、样式绘制,如果在某个阶段执行时间很长,超过 16.6ms,那么就会阻塞页面的渲染,从而出现卡顿现象,也就是常说的掉帧。

以上来自 

如果dom树特别大, 就会导致 js运行时间长 导致 绘制时间往后拖延, 给人视觉上就是 卡顿的效果

情况类似如下

了解决堵塞问题 我们引入了 并发模式(红绿灯)和 nextUnitOfWork fiber (将 react 递归渲染 切割为 单独一个小块小块 类似计程车 🚗)

类似如下

使用增量渲染(把渲染任务拆分成块,匀到多帧),将把工作分解成小单元,在完成每个单元之后,如果还有其他任务需要完成,我们将让浏览器中断渲染,也就是经常听到的fiber。

关键点:

  • 增量渲染

  • 更新时能够暂停,终止,复⽤渲染任务

  • 给不同类型的更新赋予优先级

并发模式 红绿灯🚥

实现fiber的核心是window.requestIdleCallback()window.requestIdleCallback()⽅法将在浏览器的空闲时段内调⽤的函数队列。关于window.requestIdleCallback()请点击查看文档。

你可以把 requestIdlecallback 想象成一个 setTimeout,但是浏览器不会告诉你它什么时候运行,而是在主线程空闲时运行回调。

window.requestIdleCallback将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。

我们使用window.requestIdleCallback来实现代码

// 下一个工作节点
let nextUnitOfWork = null as FiberProps | null | undefined;

function workLoop(deadline: any) {
  let shouldYield = false
  // 如果存在下一个工作节点 就继续 执行
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

Fiber

将 虚拟DOM 递归渲染顺序 变为 后序遍历 遍历相关 可看文章  从 React 源码中学到的非递归先序遍历和后序遍历算法

fiber树结构格式为

// 单个工作格类型
export type FiberProps = VDOMProps & {
  /** 真实dom节点*/
  dom: Element | null;
  /** 父节点工作格 */
  parent?: FiberProps;
  /** 子节点工作格 父节点下第一个子节点 */
  child?: FiberProps;
  /** 相邻工作格 相邻的下一个兄弟节点 */
  sibling?: FiberProps;
  /** 属性 */
  props: Omit<VDOMProps, 'children'> & {
    children: FiberProps[]
  }
}

通过 parent child sibling 形成链表, 可以找到 下一个工作节点 nextUnitOfWork

顺序为 先父节点 --- 第一个子节点 ---- 是否有子节点 继续 ---- 无子节点 就 兄弟节点 ---- 无兄弟节点

fiber生成顺序为 A B D E C F

初始化render

初始化 render 和 第一个 nextUnitOfWork 工作节点

import type { VDOMProps, FiberProps, Element } from '../shared';

function createDom(fiber: FiberProps): Element {
  const dom =
    fiber.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type)

      const isProperty = (key: string) => key !== "children"
  Object.keys(fiber.props)
    .filter(isProperty)
    .forEach(name => {
      // @ts-ignore
      dom[name] = fiber.props[name]
    })

  return dom
}

/**
 * 初始化第一个fiber节点
 * */ 
function render(vDom: VDOMProps , container: Element) {
  nextUnitOfWork = {
    dom: container,
    props: {
      children: [vDom],
    },
  } as FiberProps
}

// 下一个工作节点
let nextUnitOfWork = null as FiberProps | null | undefined;

创建fiber

1、创建fiber 当前fiber 生成dom

function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
  // 当前节点 生成dom
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
	// 当前节点 插入dom中
  if (fiber.parent?.dom) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
}

2、对子节点进行遍历 生成新的fiber

child 代表第一个 子节点, sibling代表 下一个兄弟元素

function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
  ...
   // children 生成fiber
  const elements = fiber.props.children
  let index = 0
  let prevSibling = null

  while (index < elements.length) {
    const element = elements[index]

    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    }

    if (index === 0) {
      fiber.child = newFiber
    } else {
      if (prevSibling) {
        (prevSibling as FiberProps).sibling = newFiber
      }
    }

    prevSibling = newFiber
    index++
  }
}

3 返回下一个工作节点

function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
  ...
  // 如果有子节点工作格, 返回子工作格
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber

  while (nextFiber) {
    // 如果有兄弟节点  返回相邻兄弟工作格
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    // 如果没有 返回上一级
    nextFiber = nextFiber.parent as FiberProps;
  }
}

以上 就实现了 将VDOM 生成 单个工作格Fiber 链表, 并进行渲染

代码链接 github.com/beewolf233/…

参考文章

手把手带你写一个mini版的React,掌握React的基本设计思想

从 React 源码中学到的非递归先序遍历和后序遍历算法