二、实现任务调度器

2024-03-23  本文已影响0人  sweetBoy_9126

问题:dom 树特别大导致渲染卡顿
原因:js 单线程,在执行我们的某段逻辑就会阻塞后续的渲染
而我们之前的 render 函数就是通过递归实现的,如果dom节点特别多一样会导致卡顿

解决思路:拆分,将一个大任务拆成多个小任务,每个小任务都足够小
实现:采用 requestIdleCallback 分帧运算

function workLoop(deadline) {
  console.log(deadline.timeRemaining())
  let shouldYield = false
  while(!shouldYield) {
    // 这里为什么是小于 1
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop)

实现 fiber

问题:如何做到每次只渲染几个节点呢?
如何在下次执行的时候依然从之前的位置执行?
解决思路:
把树结构转变成链表结构

  1. child
  2. sibling
  3. parent.sibling
    如果当前节点有子节点,下一个节点就是子节点,如果没有子节点下一个节点就是兄弟节点,如果连兄弟节点都没有下一个节点就是父亲的兄弟节点

上图变成链表就是: a->b->d->e->c->f->g

实现:
performUnitOfWork

  1. 创建dom
  2. 把 dom 添加到父容器内
  3. 设置 dom 的 props
  4. 建立关系 chil sibling parent
  5. 返回下一个节点
const createTextNode = (text) => {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: text,
      children: []
    },
  }
}

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => typeof child === 'string' ? createTextNode(child) : child)
    }
  }
}
const render = (el, container) => {
  console.log(1)
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el]
    }
  }
}

// 下一个任务(节点)
let nextWorkOfUnit = null
function workLoop(deadline) {
 console.log(3) 
  let shouldYield = false
  while(!shouldYield && nextWorkOfUnit) {
    console.log(2)
    // 返回下一个节点
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
    // 这里为什么是小于 1
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}
const createDom = (type) => {
  return type === 'TEXT_ELEMENT' ? document.createTextNode('') : document.createElement(type)
}
const updateProps = (dom, props) => {
  Object.keys(props).forEach(attr => {
    if (attr !== 'children') {
      dom[attr] = props[attr]
    }
  })
}
const initChildren = (fiber) => {
  // 4. 建立关系 child sibling parent
  const children = fiber.props.children
  let prevChild = null
  children.forEach((child, index) => {
    const newFiber = {
      type: child.type,
      props: child.props,
      child: null, // child 和 sibling 初始化我们不知道
      sibling: null,
      parent: fiber,
      dom: null
    }
    if (index === 0) {
      fiber.child = newFiber
    } else {
      // 如果不是第一个孩子就需要绑定到sibling,也就是上一个孩子的sibling 上,所以我们需要知道上一个孩子
      prevChild.sibling = newFiber
    }
    // 考虑到我们还需要设置 parent.sibling,因为我们是从上往下获取的,所以work肯定是顶层也就是 parent,我们只能给 child 设置,
    // 但是如果直接在child 上加就会破坏原有结构,所以我们单独维护一个newWork 对象,
    prevChild = newFiber
  })
}
const performWorkOfUnit = (fiber) => {
  if (!fiber.dom) {
    // 1. 创建dom
    const dom =(fiber.dom =  createDom(fiber.type))
    // 2. 把 dom 添加到父容器内
    fiber.parent.dom.append(dom)
    // 3. 设置 dom 的 props
    updateProps(dom, fiber.props)
  }
  initChildren(fiber)
  // 5. 返回下一个节点
  if (fiber.child) {
    return fiber.child
  }
  if (fiber.sibling) {
    return fiber.sibling
  }
  return fiber.parent.sibling

}
requestIdleCallback(workLoop)
const React = {
  render,
  createElement
}
export default React

问题

使用 requestIdleCallback 渲染完一部分节点后没有空余时间了,就不会再继续渲染其他的节点,这时候用户就只会看到一部分节点,等有空余时间了再去渲染其他节点,用户才会看到完整的节点

解决方案:
我们之前代码是每次创建完dom后紧接着添加到父级容器里,我们可以后置在最后阶段把所有的 dom 添加到容器中不要在中途添加
需要知道两个点:

  1. 链表什么时候结束
    当我们的 nextWorkOfUnit 为 null 时
  2. 需要知道根节点(需要递归的把所有dom添加到父级容器上)
    也就是我们执行 render 时候初始化的节点
const render = (el, container) => {
  root = (nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el]
    }
  })
}

let root = null
// 下一个任务(节点)
let nextWorkOfUnit = null
function workLoop(deadline) {
  let shouldYield = false
  while(!shouldYield && nextWorkOfUnit) {
    // 返回下一个节点
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
    shouldYield = deadline.timeRemaining() < 1
  }
  // 链表结束
  if (!nextWorkOfUnit && root) {
    commitRoot()
  }
  requestIdleCallback(workLoop)
}
const commitRoot = () => {
  commitWork(root.child)
  root = null
}
const commitWork = (fiber) => {
  if (!fiber) return
  fiber.parent.dom.append(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

const performWorkOfUnit = (fiber) => {
  if (!fiber.dom) {
    // 1. 创建dom
    const dom =(fiber.dom =  createDom(fiber.type))
    // 3. 设置 dom 的 props
    updateProps(dom, fiber.props)
  }
上一篇 下一篇

猜你喜欢

热点阅读