这是我参与更文挑战的第21天,活动详情查看: 更文挑战
大家好我是小村儿,在上节Fiber算法开发环境的基本配置,大家也可以看源码跟着学习,还介绍了一下核心API ‘requestIdelCallback’,介绍了Fiber核心思路,我们接下来实现Fiber算法,一探究竟。
Fiber算法准备工作: 创建任务队列并添加任务
1. 准备一段JSX代码
先准备一段JSX代码,我们下面就看看如何利用Fiber算法如何一步一步将JSX代码转换成真实DOM对象,然后显示在显示在页面当中。
import React from "./react"
const jsx = (<div>
<p>Hello React</p>
</div>)
console.log(jsx)
复制代码
2. 创建createElement方法将JSX转化为VirtualDOM对象
JSX解析需要一个React.createElement方法,我们创建一个react文件,创建一个react文件夹,默认导出一个类,这个类有一个createElement方法,这个方法很简单就是我们之前创建过的createElement
方法复制过来即可
// react/index.js
import createElement from './createElement';
export default {
createElement
}
// CreateElement/indexjs
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)
}
}
复制代码
暂时简化一些,就不需要返回children。
目录结构:
查看效果:
成功获得VirtualDOM对象!!!
3. 创建一个生成任务队列函数将VirtualDOM对象转化到任务队列中
接下来我们要把这个VirtualDOM渲染到页面当中,则需要一个render
方法,这个render
方法也需要从 react 文件夹中的index.js中导出,所以我们先创建一个文件夹reconciliation
,在这里面的index.js文件下我们创建render
方法, Fiber算法会将VirtualDOM转化为一个个小任务。所以在在render中我们需要在
- 向任务队列中添加任务
- 指定在浏览器空闲时执行任务
之后这些任务就是通过vdom对象构建fiber对象
所以我们在react文件夹下创建一个Misc文件夹,代表是杂项的意思,什么意思呢?就是当我们处理主业务的时候,肯定有一些辅助的函数,这些辅助的函数就可以放入这个杂项的文件下进行管理 ,我们再在Misc文件夹下创建一个CreateTaskQueue
文件夹,这个文件夹用来创建任务队列,CreateTaskQueue函数返回一个对象,返回一个对象有两个属性,push,pop完成队列的先进先出操作
// 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())
}
// react/index.js
import createElement from './createElement';
export {render} from "./reconciliation" // 导出render方法
复制代码
这样我们就可以将vdom转化成任务队列了
4. 小结
我们先准备一段JSX代码,babel会将这JSX一段代码转化成React.createElement
方法调用,所以我们在次实现一下createElement
方法(看了我之前tinyReact的)直接复制过来即可,这样就可以将 JSX 转化为VirtualDOM
对象。我们在react文件夹下导出一个对象,将createElement
放入该对象中,完成React.createElement
,我们在创建一个文件夹reconciliation
在这个文件夹下放下Fiber算法的核心逻辑,在这个文件夹下的index里面有一个render
方法并导出,在react/index.js再引用并导出,所以在src/index.js
才能按需导出render方法,render
方法需要两个参数element
, dom
。dom
是父级,element
是子级。接下来我们要从元素的最顶层依次去向下查找查找到每一个元素的VirtualDOM,我们要为每个VirtualDOM创建fiber对象,所以我们创建一个任务队列,每添加的一个任务就是一个对象,包含了父级,包含了子级。dom就是最外层的父级。我们还创建了一个Misc
文件夹,这个文件夹叫做杂项,在实现Fiber算法的时候呀,肯定会有一些辅助方法,这些辅助方法都会放在Misc中。我们在这个杂项文件家中创建第一个辅助方法,createTaskQueue
,这个方法就是用来创建任务队列的,为什么要创建任务队列呢?因为我们要执行的任务不止一个,我们在执行任务之前,都要把这些任务放入这个任务队列当中,createTaskQueue
返回一个对象,对象有两个属性,push 和 pop,push
:向任务队列中添加任务, pop
:从任务队列中获取任务, 这样达到方便Fiber算法内部调动。
实现任务的调度逻辑
上面我们完成了Fiber算法的基本目录结构,和创建了Fiber算法需要的任务队列,并且完成了添加任务准备工作,接下来我们来实现任务调度逻辑
1. 在render方法内调用requestIdelCallback
我们在render方法中调用Fiber算法核心API requestIdelCallback
,完成指定在浏览器空闲时执行任务,我们将执行任务函数名取为performTask
,意思就是执行任务的意思
export const render = (element, dom) => {
/**
* 指定在浏览器空闲的时间去执行任务
*/
requestIdleCallback(performTask)
}
复制代码
2. 实现performTask方法
我们之前说过Fiber算法会将一个大任务拆分成一个一个小任务,一个个小任务就需要采用循环的方式来调用,所以performTask做的第一件事是循环调用一个个小任务,我们将这件事处理函数命名为workLoop。将deadline传递进去。
const performTask = deadline => {
// 将一个大任务拆解成一个个小任务并且循环处理
workLoop(deadline)
}
复制代码
3. 实现workLoop方法
workLoop 是用来循环处理一个个小任务的,并且接收deadline这个参数。
- 当任务不存在
这个方法第一件事情就是判断当前要执行任务存不存在,(我们在顶部声明一个常量 subTask(子任务),默认值为null
,这个任务呢在taskQueue
中获取)。如果任务不存在则去taskQueue里面去获取并赋值给subTask,获取方法名我们取为getFirstTask
(获取任务队列第一个任务),我们暂时把这个方法置空即可。
const getFirstTask = () => {}
const workLoop = deadline => {
if(!subTask) {
subTask = getFirstTask()
}
}
复制代码
- 当任务存在
如果任务存在并且浏览器有空余时间我们则需要执行这个任务,而且这个subTask任务不止一个,所以我们采用循环的方法去执行这个任务,循环里面执行任务的操作,我们再封装一个函数executeTask代表执行任务的意思,executeTask
执行完以后必须返回一个新的任务回来,只有返回一个新的任务这个while循环才能继续去执行,executeTask
并且接收一个参数,实际上写个参数就是已经是fiber对象了,今天我们只实现任务的调度逻辑,fiber对象的实现我们暂且留一个坑。
const workLoop = deadline => {
if(!subTask) {
subTask = getFirstTask()
}
// 如果任务存在且浏览器存在空闲时间就去执行这个任务
while(subTask && deadline.timeRemaining() > 1) {
subTask = executeTask()
}
}
复制代码
- 当有更高级的任务被执行的情况
我们需要考虑的一种情况, 任务在执行的过程中,浏览器这时候有一个更高优先级的任务要执行那么浏览器没有空余时间,这个任务执行就会被打断,那么workLoop
函数执行完退出,这时候的performTask
就执行到最后就结束了。
但是我们有可能任务还没处理完,如果等到高级任务被执行完成我们必须重新去注册这个任务。
也就是说我们在performTask
最后面,不但还要去判断下subTask
是否有值,(有值,就说明任务还没有执行完)。而且还要判断taskQueue
里面是否有任务,(如果taskQueue
里面还有任务的话我们还是要在浏览器空闲的时候去执行任务).
这里我们就需要在taskQueue
里面实现一个方法isEmpty判断是否存在任务.
所以当subTask
还有值或者taskQueue.isEmpty
为false
我们还需要调用requestIdleCallback(performTask)
// react/Misc/CreateTaskQueue/index.js
const createTaskQueue = () => {
const taskQueue = [];
return {
···
/**
* 判断任务队列中是否还有任务
*/
isEmpty: () => taskQueue.length === 0
}
}
export default createTaskQueue
// 调度任务
const performTask = deadline => {
workLoop(deadline)
if(subTask || !taskQueue.isEmpty) {
requestIdleCallback(performTask)
}
}
复制代码
4. 实现executeTask
上面也说了executeTask
这个方法是执行任务,并且返回一个新的任务,且需要的一个参数就是Fiber对象
const executeTask = fiber => {
return newSubTask
}
复制代码
暂时executeTask
先实现伪代码,第十天我们在揭晓
总结
今天我们主要是做了三件事:
- 创建基本目录
以一段JSX代码作为出发点,构建了一个实现Fiber算法的目录结构,创建一个React文件夹,下面有三个文件夹和一个index.js文件,reconciliation是存放Fiber算法核心代码目录,Misc(杂项)存放在处理Fiber核心算法辅助代码目录,CreateElement这里存放createElemnt方法。
- 完成创建任务队列并添加任务
在Misc目录中实现一个创建任务队列方法createTaskQueue返回一个任务队列,然后在reconciliation的render方法中添加任务
3.实现任务的调度逻辑
使用requestIdleCallback
API和循环实现任务的调度逻辑,在requestIdleCallback调用performTask方法,这里面在循环调用任务workLoop,workLoop考虑有任务和没任务的情况,没任务去taskQueue获取任务,有的话且浏览器有空闲时间则执行任务,执行任务完之后让循环继续则需要返回新任务,在这个任务执行的过程还需要考虑浏览器是否需要处理高级任务,当高级任务处理完成,还需要查看任务队列是否还有任务subTask是否还有任务有则重新使用requestIdleCallback
API重新循环上面操作。
好了今天就完成这三件事,我们可以不仅学习Fiber算法思想,还可以学习目录结构的思想,还可以学习到代码组织技巧,还有命名很重要哈哈哈,后面我们会学习Fiber对象的构建,敬请期待!!!如果你能坚持看到这里,希望点赞,或者评论说出你的疑惑点,共同学习,谢谢!!!