Skip to content

创建任务队列和完成任务调度逻辑

createElement 方法

React.createElement 方法,来讲 jsx 转换为 virtualDOM 对象

js
// index.js
import createElement from './CreateElement'
export default {
  createElement
}

// CreateElement/index.js
export default function createElement(type, props, ...children) {
  const childElements = [].concat(...children).reduce((result, child) => {
    if (child !== false && child !== true && child !== null) {
      if (child instanceof Object) {
        result.push(child)
      } else {
        result.push(createElement('text', { textContent: child }))
      }
    }
    return result
  }, [])
  return {
    type,
    props: Object.assign({ children: childElements }, props)
  }
}

生成任务队列函数

我们将 virtualDOM 渲染到页面中,则需要一个 render 方法,这个 render 方法也需要从 react 文件夹中的 index.js 中导出,所以我们先创建一个文件夹 reconciliation,在这里面创建 render 方法。Fiber 方法会将 virtualDOM 转换成一个个小任务,所以在 render 中我们需要

  1. 向任务队列中添加任务
  2. 指定在浏览器空闲时执行任务
js
// Misc/CreateTaskQueue/index.js
const createTaskQueue = () => {
  const taskQueue = []
  return {
    /**
     * 向任务队列中添加任务
     */
    push: item => taskQueue.push(item),
    /**
     * 从任务队列中获取任务
     */
    pop: () => taskQueue.shift()
  }
}
export default createTaskQueue

// reconciliation/index.js
import { createTaskQueue } from '../Misc'
const taskQueue = createTaskQueue()
export const render = (element, dom) => {
  /**
   * 1. 向任务中添加任务
   * 2. 指定在浏览器空闲时执行任务
   * 任务就是通过 vdom 对象 构建 fiber 对象
   */
  taskQueue.push({
    dom,
    props: { children: element }
  })
  console.log(taskQueue.pop())
}

// index.js
import createElement from './createElement'
export { render } from './reconciliation' // 导出render方法

这样我们就可以将 vdom 转换成任务队列了

image-20230117173045346

实现任务调度逻辑

我们在 render 方法中调用 Fiber 算法核心 API requestIdelCallback,完成指定在浏览器空闲时执行任务,我们将任务函数取名为 performTask

  1. 在render 方法内调用 requestIdleCallback

    js
    export const render = (element, dom) => {
     /**
      * 指定在浏览器空闲的时间去执行任务
      */
     requestIdleCallback(performTask)
    }
  2. 实现 performTask 方法

    Fiber 算法会将一个大任务拆分成一个一个小任务,一个个小任务就需要采用循环的方式来调用,所以 performTask 做的第一件事就是循环调用,我们将这个事件处理函数命名为 workLopp,并将 deadline 传递进去

    js
    const performTask = deadline => {
      // 将一个大任务拆解成一个个小任务并且循环处理
      workLoop(deadline)
    }
  3. 实现 workLoop 方法

    workLoop 是用来循环处理一个个小任务的,并且接收 deadline 这个参数

    当任务不存在

    第一件事就是判断当前要执行的任务存不存在,如果不存在则去 taskQueue 里面获取并赋值给 subTask,获取方法名我们取名为 getFirstTask

    js
    // 要执行的子任务
    let subTask = null
    // 不是获取任务队列中的第一个任务,而是任务队列的第一个子任务
    const getFirstTask = () => {}
    
    const workLoop = deadline => {
      if(!subTask) {
        subTask = getFirstTask()
      }
    }

    当任务存在

    如果任务存在并且浏览器有空余时间我们则需要执行这个任务,而且这个 subTask 任务不止一个,所以我们采用循环的方法去执行这个任务。我们再封装一个函数 executeTask 代表执行任务,executeTask 执行完以后必须返回一个新的任务回来,只有返回一个新的任务这个 while 循环才能继续去执行,executeTask 并且接收一个参数,实际上写个参数就是 fiber 对象

    js
    const workLoop = deadline => {
      if(!subTask) {
        subTask = getFirstTask()
      }
      
      // 如果任务存在且浏览器存在空闲时间,就执行这个任务,并接收任务返回新的任务
      while(subTask && deadline.timeRemaining() > 1) {
        subTask = executeTask()
      }
    }

    当有更高级的任务被执行的情况

    我们需要考虑一种情况,任务在执行的过程中,浏览器这时候有一个更高优先级的任务要执行,那么浏览器没有空余时间,这个任务执行就会被打断,那么 workLoop 函数执行完退出,这时候 performTask 就执行到最后结束了

但是我们有可能任务还没处理完,如果等到高级任务被执行完成我们必须重新去注册这个任务

  • 也就是说我们在 performTask 最下面,不但还要去判断下 subTask 是否有值。而且还要判断 taskQueue 里面是否有任务
js
// Misc/CreateTaskQueue/index.js
const createTaskQueue = () => {
  const taskQueue = []
  return {
    /**
     * 判断任务队列中是否还有任务
     */
    isEmpty: () => taskQueue.length === 0
  }
}
export default createTaskQueue

// reconciliation/index.js
const performTask = deadline => {
  workLoop(deadline)
  if(subTask || !taskQueue.isEmpty) {
    requestIdleCallback(performTask)
  }
}

executeTask 这个方法是执行任务,并且返回一个新的任务,且需要一个参数就是 Fiber 对象

js
const executeTask = fiber => {
  return newSubTask
}

常备不懈,才能有备无患