创建任务队列和完成任务调度逻辑
createElement 方法
React.createElement
方法,来讲 jsx 转换为 virtualDOM 对象
// 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 中我们需要
- 向任务队列中添加任务
- 指定在浏览器空闲时执行任务
// 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 转换成任务队列了
实现任务调度逻辑
我们在 render 方法中调用 Fiber 算法核心 API requestIdelCallback
,完成指定在浏览器空闲时执行任务,我们将任务函数取名为 performTask
在render 方法内调用 requestIdleCallback
jsexport const render = (element, dom) => { /** * 指定在浏览器空闲的时间去执行任务 */ requestIdleCallback(performTask) }
实现 performTask 方法
Fiber 算法会将一个大任务拆分成一个一个小任务,一个个小任务就需要采用循环的方式来调用,所以 performTask 做的第一件事就是循环调用,我们将这个事件处理函数命名为 workLopp,并将 deadline 传递进去
jsconst performTask = deadline => { // 将一个大任务拆解成一个个小任务并且循环处理 workLoop(deadline) }
实现 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 对象
jsconst workLoop = deadline => { if(!subTask) { subTask = getFirstTask() } // 如果任务存在且浏览器存在空闲时间,就执行这个任务,并接收任务返回新的任务 while(subTask && deadline.timeRemaining() > 1) { subTask = executeTask() } }
当有更高级的任务被执行的情况
我们需要考虑一种情况,任务在执行的过程中,浏览器这时候有一个更高优先级的任务要执行,那么浏览器没有空余时间,这个任务执行就会被打断,那么 workLoop 函数执行完退出,这时候 performTask 就执行到最后结束了
但是我们有可能任务还没处理完,如果等到高级任务被执行完成我们必须重新去注册这个任务
- 也就是说我们在 performTask 最下面,不但还要去判断下 subTask 是否有值。而且还要判断 taskQueue 里面是否有任务
// 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 对象
const executeTask = fiber => {
return newSubTask
}