美文网首页
二、实现任务调度器

二、实现任务调度器

作者: sweetBoy_9126 | 来源:发表于2024-03-23 17:19 被阅读0次

问题: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)
  }

相关文章

  • Quartz简单使用

    基础概念 Scheduler - 与调度器交互的主要API。 Job - 需要被调度器调度的任务必须实现的接口。 ...

  • Quartz调度系统入门和调度高可用实现方案

    ** 版本:2.2.1 ** Hello world: 调度器: 任务详情:任务体实现Job接口 触发器: 执行调...

  • 分布式调度器Quartz解读

    术语: scheduler:任务调度器 job: 被调度的任务 trigger:触发器,用于定义Job调度时间规则...

  • okhttp实现分析

    本文概要 okhttp拦截器实现 代理和路由 连接池实现 任务调度 1、okhttp拦截器实现 首先看下okhtt...

  • 分布式任务调度系统设计

    一、思路 任务调度器、任务执行器、任务 任务调度器不关心业务逻辑,只关心任务的触发策略、失败策略、路由策略、阻塞处...

  • 任务调度器

    题目描述:给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种...

  • 任务调度器

    Quartz Scheduler 开源任务调度框架, simple use 1.注册Job 方式1,实现job接口...

  • 定时任务框架Quartz

    定时任务框架! 定时任务就是分为三个模块:任务、触发器、调度器 过程就是,调度器协调触发器来再固定时间去触发任务!

  • quartz的监听器listener

    一、scheduler(调度器)监听 二、job(任务)监听 三、trigger(触发器)监听 四、执行顺序、触发...

  • 分布式任务调度 SchedulerX

    参考文档: 为应用实现任务调度(EDAS 部署) 什么是分布式任务调度SchedulerX?分布式任务调度Sche...

网友评论

      本文标题:二、实现任务调度器

      本文链接:https://www.haomeiwen.com/subject/punhtjtx.html