这是我参与更文挑战的第21天,活动详情查看: 更文挑战react
你们好我是小村儿,在上节Fiber算法开发环境的基本配置,你们也能够看源码跟着学习,还介绍了一下核心API 'requestIdelCallback',介绍了Fiber核心思路,咱们接下来实现Fiber算法,一探究竟。git
项目代码, 你们能够持续跟进学习,共勉!!!github
先准备一段JSX代码,咱们下面就看看如何利用Fiber算法如何一步一步将JSX代码转换成真实DOM对象,而后显示在显示在页面当中。算法
import React from "./react"
const jsx = (<div> <p>Hello React</p> </div>)
console.log(jsx)
复制代码
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。 目录结构:babel
查看效果: markdown
成功得到VirtualDOM对象!!!dom
接下来咱们要把这个VirtualDOM渲染到页面当中,则须要一个render
方法,这个render
方法也须要从 react 文件夹中的index.js中导出,因此咱们先建立一个文件夹reconciliation
,在这里面的index.js文件下咱们建立render
方法, Fiber算法会将VirtualDOM转化为一个个小任务。因此在在render中咱们须要在svn
以后这些任务就是经过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转化成任务队列了
咱们先准备一段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算法须要的任务队列,而且完成了添加任务准备工做,接下来咱们来实现任务调度逻辑
咱们在render方法中调用Fiber算法核心API requestIdelCallback
,完成指定在浏览器空闲时执行任务,咱们将执行任务函数名取为performTask
,意思就是执行任务的意思
export const render = (element, dom) => {
/** * 指定在浏览器空闲的时间去执行任务 */
requestIdleCallback(performTask)
}
复制代码
咱们以前说过Fiber算法会将一个大任务拆分红一个一个小任务,一个个小任务就须要采用循环的方式来调用,因此performTask作的第一件事是循环调用一个个小任务,咱们将这件事处理函数命名为workLoop。将deadline传递进去。
const performTask = deadline => {
// 将一个大任务拆解成一个个小任务而且循环处理
workLoop(deadline)
}
复制代码
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)
}
}
复制代码
上面也说了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对象的构建,敬请期待!!!若是你能坚持看到这里,但愿点赞,或者评论说出你的疑惑点,共同窗习,谢谢!!!