React Fiber是对核心算法的一次重新实现
React Fiber的方式 破解JavaScript中同步操作时间过长的方法其实很简单——分片。
把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
React
Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。维护每一个分片的数据结构,就是Fiber。
源码地址: https://github.com/qifutian/learngit/tree/main/Fiber
1. 开发环境配置
1.3 环境配置
定义server.js,创建web服务器
import express from "express"const app = express()app.use(express.static("dist"))const template = `<html><head><title>React Fiber</title></head><body><div id="root"></div><script src="bundle.js"></script></body></html> `app.get("*", (req, res) => {
res.send(template)
})app.listen(3000, () => console.log("server is running"))
webpack.config.serve.js 配置服务端打包
const path = require("path")
const nodeExternals = require("webpack-node-externals")module.exports = {
target: "node",mode: "development",entry: "./server.js",output: {
path: path.resolve(__dirname, "build"),filename: "server.js"},module: {
rules: [{
test: /\.js$/,exclude: /node_modules/,use: {
loader: "babel-loader"}}]},externals: [nodeExternals()]
}
修改babel.config.json
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
修改package.json
针对serve.js进行打包
运行命令,执行build下的server.js打包
start运行进行统一运行
{
"name": "fiber","version": "1.0.0","main": "babel.config.js","scripts": {
"start": "npm-run-all --parallel dev:*","dev:server-compile": "webpack --config webpack.config.server.js --watch","dev:server": "nodemon ./build/server.js","dev:client-compile": "webpack --config webpack.config.client.js --watch"},"keywords": [],"author": "","license": "ISC","devDependencies": {
"@babel/core": "^7.11.6","@babel/preset-env": "^7.11.5","@babel/preset-react": "^7.10.4","babel-loader": "^8.1.0","nodemon": "^2.0.4","npm-run-all": "^4.1.5","webpack": "^4.44.2","webpack-cli": "^3.3.12","webpack-node-externals": "^2.5.2"},"dependencies": {
"express": "^4.17.1"},"description": ""
}
客户端打包配置 wenpack.config.client.js
const path = require("path")module.exports = {
target: "web",mode: "development",entry: "./src/index.js",output: {
path: path.resolve(__dirname, "dist"),filename: "bundle.js"},devtool: "source-map",module: {
rules: [{
test: /\.js$/,exclude: /node_modules/,use: {
loader: "babel-loader"}}]}
}
requestIdleCallback
核心API 功能介绍
利用浏览器的空闲时间执行任务,如果有更高优先级的任务要执行时,当前执行的任务可以被终止,优先执行更高级别任务
应用场景:例如有一个计算任务要执行,计算任务需要花费比较长时间执行,在执行任务时候,浏览器会被一直占用,不能执行其他任务,浏览器不能响应,体验不好。可以利用requestIdleCallback注册事件,利用空余时间执行,高任务先执行,执行完之后,利用空余时间执行
是window下一个方法,可以直接调用
requestIdleCallback(function(deadline) {
// deadline.timeRemaining() 获取浏览器的空余时间,根据事件多少,进行是不是执行该任务
})
浏览器空余时间说明
页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时, 用户会感觉到卡顿
1s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,如果每一帧执行的时间小于16ms,就说明浏览器有空余时间
如果任务在剩余的时间内没有完成则会停止任务执行,继续优先执行主任务,也就是说 requestIdleCallback 总是利用浏览器的空余时间执行任务
API 功能体验
页面中有两个按钮和一个DIV,点击第一个按钮执行一项昂贵的计算,使其长期占用主线程,当计算任务执行的时候去点击第二个按钮更改页面中 DIV 的背景颜色。
使用 requestIdleCallback 就可以完美解决这个卡顿问题。
单纯执行计算任务之后再点击修改颜色会很卡顿
<div class="playground" id="play">playground</div>
<button id="work">start work</button>
<button id="interaction">handle some user interaction</button>
<style>.playground {
background: palevioletred;padding: 20px;margin-bottom: 10px;}
</style>
var play = document.getElementById("play")
var workBtn = document.getElementById("work")
var interactionBtn = document.getElementById("interaction")
var iterationCount = 100000000
var value = 0var expensiveCalculation = function (IdleDeadline) {
while (iterationCount > 0 && IdleDeadline.timeRemaining() > 1) {
value =Math.random() < 0.5 ? value + Math.random() : value + Math.random()iterationCount = iterationCount - 1}requestIdleCallback(expensiveCalculation)
}workBtn.addEventListener("click", function () {
requestIdleCallback(expensiveCalculation)
})interactionBtn.addEventListener("click", function () {
play.style.background = "palegreen"
})
Fiber
在react 16 中,官方对于内部代码做了大量的重写,其中 Fiber就是重要的一部分。
什么是 React Fiber ?
- 就是一种react比对virtual dom的一种新的算法,fiber是算法名字
- 之前的对比方式是采用循环加递归的方式实现的 ,有一个问题,就是递归需要层层进入,不能中断,组件特别多,主线程被占用,会产生卡顿,执行时间耗时长
解决方式
- 利用浏览器空闲时间执行任务,拒绝长时间占用主线程
- 放弃递归只采用循环,因为循环可以被中断
- 任务拆分,任务的单元必须要小,将任务拆分成一个个的小任务,重新执行的耗费小很多
实现思路
Fiber是具体实现
在 Fiber 方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:
- 构建 Fiber (可中断) virtual dom对象的比对
- 提交 Commit (不可中断) 真实dom的更新
DOM 初始渲染: virtualDOM -> Fiber -> Fiber[] -> DOM
DOM 更新操作: newFiber vs oldFiber -> Fiber[] -> DOM
具体过程
- 使用react时候,依然使用jsx
- babel将jsx语法转换为react.createElement方法的调用
- 调用后,但会virtual dom对象
- 接下来执行第一个阶段,一阶段就是构建Fiber对象,采用循环方式,在这个virtual dom对象中找到每一个内部的vitual
dom,为每一个virtaul dom对象构建Fiber对象 - Fiber中存储了很多节点的信息,很重要的一个就是当前节点执行的操作,是删除,更新,会存放在一个数组当中
- 接下来就是第二阶段操作,循环Fiber数组,循环过程中,根据Fiber对象中存放的目前节点的操作类型,将这个操作应用在真实节点上,这就是执行的大概流程
Fiber 对象
// fiber对象预览
{type 节点类型 (元素, 文本, 组件)(具体的类型)props 节点属性stateNode 节点 DOM 对象 | 组件实例对象tag 节点标记 (对具体类型的分类 hostRoot || hostComponent || classComponent || functionComponent)effects 数组, 存储需要更改的 fiber 对象effectTag 当前 Fiber 要被执行的操作 (新增, 删除, 修改)parent 当前 Fiber 的父级 Fiberchild 当前 Fiber 的子级 Fibersibling 当前 Fiber 的下一个兄弟 Fiberalternate Fiber 备份 fiber 比对时使用
}
创建任务队列并添加任务
准备一段jsx代码,查看Fiber如何将jsx转化
- 首先在src下index.js,准备了jsx代码,准备将代码显示在浏览器中
- 将jsx准换成createElement来调用
- 在react文件夹下创建index.js ,导出createElement方法,在src下的index.js直接调用
- react下创建reconciliation文件夹,放置fiber算法的核心逻辑,index.js有render方法,render方法接收element和dom,从上向下查找,找到每一个dom去创建fiber对象,利用taskQueue.push
- Misc放置杂项文件夹,createTaskQueue创建任务队列,pop中使用shilf方法是因为先进先出
index.js
import React,{
render} from './react' // 自己实现的简易react对象
const root = document.getElementById("root")
const jsx = (<div><p>Hello React</p><p>Hi Fiber</p></div>
)console.log(jsx)
src/react/index.js
import createElement from "./CreateElement"
export {
render } from "./reconciliation"
export {
Component } from "./Component"export default {
createElement
}
创建src/react/reconciliation/index.js
import {
createTaskQueue, // 导入 创建任务队列
} from "../Misc"
export const render = (element,dom) => {
// 1. 向任务队列中添加任务// 2. 在浏览器空闲时候执行任务// 任务就是通过vdom 对象构建fiber 对象// 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列taskQueue.push({
dom, // dom节点props: {
children: element } // 属性 }) // 向任务队列添加任务,任务其实就是js对象
}
src/react/Misc/CreateTaskQueue/index.js
const createTaskQueue = () => {
const taskQueue = [] return {
/*** 向任务队列中添加任务*/push: item => taskQueue.push(item), // item 代表任务/*** 从任务队列中获取任务*/pop: () => taskQueue.shift(), // 从数组前面将任务取出/*** 判断任务队列中是否还有任务*/isEmpty: () => taskQueue.length === 0}
}export default createTaskQueue
src/react/Misc/index.js
// 从CreateTaskQueue文件夹下index.js引入,再导出createTaskQueue这个方法
export {
default as createTaskQueue } from "./CreateTaskQueue"
实现任务队列调度逻辑
- 在任务调度中,调用render方法,向任务队列中添加任务,在浏览器空闲时间执行任务
- 在performTask中,调用workLoop方法执行任务,判断任务实参还有,是否执行
- workLoop专门执行任务,如果子任务不存在去获取子任务,如果存在且浏览器存在空闲时间,调用executeTask接收任务,返回新任务
src/react/reconciliation/index.js
import {
createTaskQueue, // 导入 创建任务队列
} from "../Misc"const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () = > {
}
const executeTask = fiber = > {
}
const workLoop = deadline =>{
// 调用任务需要先判断是否存在任务if(!subTask){
subTask = getFirstTask() // 没有任务调用获取}/*** 如果任务存在并且浏览器有空余时间就调用* executeTask 方法执行任务 接受任务 返回新的任务*/while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)}if (pendingCommit) {
// 任务更高级,打断当前任务,将任务保存起来commitAllWork(pendingCommit)}
}
const performTask = deadline = => {
// 当前方法不负责执行任务,只负责调度任务// 获取浏览器的空闲时间workLoop(deadline)/*** 判断任务是否存在* 判断任务队列中是否还有任务没有执行* 再一次告诉浏览器在空闲的时间执行任务*/if (subTask || !taskQueue.isEmpty()) {
requestIdleCallback(performTask)}
}
export const render = (element,dom) => {
// 1. 向任务队列中添加任务// 2. 在浏览器空闲时候执行任务// 任务就是通过vdom 对象构建fiber 对象// 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列taskQueue.push({
dom, // dom节点props: {
children: element } // 属性 }) // 向任务队列添加任务,任务其实就是js对象requestIdleCallback(performTask)
}
构建根节点的Fiber对象
当前执行的任务就是为每一个节点构建Fiber对象
就是创建 1 的节点,任何创建2,3,并且构建他们之间的关系
需要注意,只有第一个子集才是父集的子集,第二个子集是第一个子集的下一个兄弟节点
src/react/reconciliation/index.js
getFirstTask中拿到task对象,就需要在while中调用循环,就是最外层根节点传递给 executeTask
import {
createTaskQueue, // 导入 创建任务队列
} from "../Misc"const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () => {
/*** 从任务队列中获取任务*/const task = taskQueue.pop()if (task.from === "class_component") {
const root = getRoot(task.instance)task.instance.__fiber.partialState = task.partialStatereturn {
props: root.props,stateNode: root.stateNode,tag: "host_root",effects: [],child: null,alternate: root}}/*** 返回最外层节点的fiber对象*/return {
props: task.props,stateNode: task.dom,tag: "host_root",effects: [],child: null,alternate: task.dom.__rootFiberContainer}
}
const executeTask = fiber = > {
}
const workLoop = deadline =>{
// 调用任务需要先判断是否存在任务if(!subTask){
subTask = getFirstTask() // 没有任务调用获取}/*** 如果任务存在并且浏览器有空余时间就调用* executeTask 方法执行任务 接受任务 返回新的任务*/while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)}if (pendingCommit) {
// 任务更高级,打断当前任务,将任务保存起来commitAllWork(pendingCommit)}
}
const performTask = deadline = => {
// 当前方法不负责执行任务,只负责调度任务// 获取浏览器的空闲时间workLoop(deadline)/*** 判断任务是否存在* 判断任务队列中是否还有任务没有执行* 再一次告诉浏览器在空闲的时间执行任务*/if (subTask || !taskQueue.isEmpty()) {
requestIdleCallback(performTask)}
}
export const render = (element,dom) => {
// 1. 向任务队列中添加任务// 2. 在浏览器空闲时候执行任务// 任务就是通过vdom 对象构建fiber 对象// 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列taskQueue.push({
dom, // dom节点props: {
children: element } // 属性 }) // 向任务队列添加任务,任务其实就是js对象requestIdleCallback(performTask)
}
构建子节点的Fiber对象
executeTask方法接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
子节点通过 fiber.props.children来获取
src/react/reconciliation/index.js
import {
createTaskQueue, // 导入 创建任务队列
} from "../Misc"const taskQueue = createTaskQueue()
const subTask = null // 默认的任务
const getFirstTask = () => {
/*** 从任务队列中获取任务*/const task = taskQueue.pop()if (task.from === "class_component") {
const root = getRoot(task.instance)task.instance.__fiber.partialState = task.partialStatereturn {
props: root.props,stateNode: root.stateNode,tag: "host_root",effects: [],child: null,alternate: root}}/*** 返回最外层节点的fiber对象*/return {
props: task.props,stateNode: task.dom,tag: "host_root",effects: [],child: null,alternate: task.dom.__rootFiberContainer}
}const reconcileChildren = (fiber, children) => {
/*** children 可能对象 也可能是数组* 将children 转换成数组*/const arrifiedChildren = arrified(children)/*** 循环 children 使用的索引*/let index = 0/*** children 数组中元素的个数*/let numberOfElements = arrifiedChildren.length/*** 循环过程中的循环项 就是子节点的 virtualDOM 对象*/let element = null/*** 子级 fiber 对象*/let newFiber = null/*** 上一个兄弟 fiber 对象*/let prevFiber = nulllet alternate = nullif (fiber.alternate && fiber.alternate.child) {
alternate = fiber.alternate.child}while (index < numberOfElements || alternate) {
/*** 子级 virtualDOM 对象*/element = arrifiedChildren[index]if (!element && alternate) {
/*** 删除操作*/alternate.effectTag = "delete"fiber.effects.push(alternate)} else if (element && alternate) {
/*** 更新*/newFiber = {
type: element.type,props: element.props,tag: getTag(element),effects: [],effectTag: "update",parent: fiber,alternate}if (element.type === alternate.type) {
/*** 类型相同*/newFiber.stateNode = alternate.stateNode} else {
/*** 类型不同*/newFiber.stateNode = createStateNode(newFiber)}} else if (element && !alternate) {
/*** 初始渲染*//*** 子级 fiber 对象*/newFiber = {
type: element.type,props: element.props,tag: getTag(element),effects: [],effectTag: "placement",parent: fiber}/*** 为fiber节点添加DOM对象或组件实例对象*/newFiber.stateNode = createStateNode(newFiber)}if (index === 0) {
fiber.child = newFiber} else if (element) {
prevFiber.sibling = newFiber}if (alternate && alternate.sibling) {
alternate = alternate.sibling} else {
alternate = null}// 更新prevFiber = newFiberindex++}
}
// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
/*** 构建子级fiber对象*/if (fiber.tag === "class_component") {
if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
fiber.stateNode.state = {
...fiber.stateNode.state,...fiber.stateNode.__fiber.partialState}}reconcileChildren(fiber, fiber.stateNode.render())} else if (fiber.tag === "function_component") {
reconcileChildren(fiber, fiber.stateNode(fiber.props))} else {
reconcileChildren(fiber, fiber.props.children)}/*** 如果子级存在 返回子级* 将这个子级当做父级 构建这个父级下的子级*/if (fiber.child) {
return fiber.child}/*** 如果存在同级 返回同级 构建同级的子级* 如果同级不存在 返回到父级 看父级是否有同级*/let currentExecutelyFiber = fiberwhile (currentExecutelyFiber.parent) {
currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat([currentExecutelyFiber]))if (currentExecutelyFiber.sibling) {
return currentExecutelyFiber.sibling}currentExecutelyFiber = currentExecutelyFiber.parent}pendingCommit = currentExecutelyFiber
}
const workLoop = deadline =>{
// 调用任务需要先判断是否存在任务if(!subTask){
subTask = getFirstTask() // 没有任务调用获取}/*** 如果任务存在并且浏览器有空余时间就调用* executeTask 方法执行任务 接受任务 返回新的任务*/while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)}if (pendingCommit) {
// 任务更高级,打断当前任务,将任务保存起来commitAllWork(pendingCommit)}
}
const performTask = deadline = => {
// 当前方法不负责执行任务,只负责调度任务// 获取浏览器的空闲时间workLoop(deadline)/*** 判断任务是否存在* 判断任务队列中是否还有任务没有执行* 再一次告诉浏览器在空闲的时间执行任务*/if (subTask || !taskQueue.isEmpty()) {
requestIdleCallback(performTask)}
}
export const render = (element,dom) => {
// 1. 向任务队列中添加任务// 2. 在浏览器空闲时候执行任务// 任务就是通过vdom 对象构建fiber 对象// 一些杂项放在Misc 文件夹中, CreateTaskQueue文件夹中放置创建任务队列taskQueue.push({
dom, // dom节点props: {
children: element } // 属性 }) // 向任务队列添加任务,任务其实就是js对象requestIdleCallback(performTask)
}
在杂项Misc中新建createStateNode/index.js
创建Fiber中的stateNode节点
import {
createDOMElement } from "../../DOM"
import {
createReactInstance } from "../createReactInstance"const createStateNode = fiber => {
if (fiber.tag === "host_component") {
return createDOMElement(fiber)} else {
return createReactInstance(fiber)}
}export default createStateNode
创建react下DOM下的index.js,导出对应的createDOMElement和updateDOMElement
export {
default as createDOMElement } from "./createDOMElement"
export {
default as updateNodeElement } from "./updateNodeElement"
Fiber对象的tag属性
host_component代表就是一个普通节点,处理子节点时候需要是动态的,是什么类型返回什么标记
Misc/getTag/index.js 处理tag对应标签
import {
Component } from "../../Component"const getTag = vdom => {
// 普通节点是字符串形式if (typeof vdom.type === "string") {
return "host_component"} else if (Object.getPrototypeOf(vdom.type) === Component) {
return "class_component"} else {
return "function_component"}
}
export default getTag
Fiber对象的effects数组
effects数组存储所有的fiber对象,在渲染的第二阶段循环该数组,统一获取fiber对象,构建真实dom对象,并且将真实的渲染到页面
// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
/*** 构建子级fiber对象*/if (fiber.tag === "class_component") {
if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
fiber.stateNode.state = {
...fiber.stateNode.state,...fiber.stateNode.__fiber.partialState}}reconcileChildren(fiber, fiber.stateNode.render())} else if (fiber.tag === "function_component") {
reconcileChildren(fiber, fiber.stateNode(fiber.props))} else {
reconcileChildren(fiber, fiber.props.children)}/*** 如果子级存在 返回子级* 将这个子级当做父级 构建这个父级下的子级*/if (fiber.child) {
return fiber.child}/*** 如果存在同级 返回同级 构建同级的子级* 如果同级不存在 返回到父级 看父级是否有同级*/let currentExecutelyFiber = fiberwhile (currentExecutelyFiber.parent) {
currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat([currentExecutelyFiber]))if (currentExecutelyFiber.sibling) {
return currentExecutelyFiber.sibling}currentExecutelyFiber = currentExecutelyFiber.parent}pendingCommit = currentExecutelyFiber
}
Fiber第二阶段
在workLoop方法中,while循环结束后,需要判断有没有pendingCommit,执行commmitAllWork方法
const commitAllWork = fiber => {
/*** 循环 effets 数组 构建 DOM 节点树*/fiber.effects.forEach(item => {
if (item.tag === "class_component") {
item.stateNode.__fiber = item}if (item.effectTag === "delete") {
item.parent.stateNode.removeChild(item.stateNode)} else if (item.effectTag === "update") {
/*** 更新*/if (item.type === item.alternate.type) {
/*** 节点类型相同*/updateNodeElement(item.stateNode, item, item.alternate)} else {
/*** 节点类型不同*/item.parent.stateNode.replaceChild(item.stateNode,item.alternate.stateNode)}} else if (item.effectTag === "placement") {
/*** 向页面中追加节点*//*** 当前要追加的子节点*/let fiber = item/*** 当前要追加的子节点的父级*/let parentFiber = item.parent/*** 找到普通节点父级 排除组件父级* 因为组件父级是不能直接追加真实DOM节点的*/while (parentFiber.tag === "class_component" ||parentFiber.tag === "function_component") {
parentFiber = parentFiber.parent}/*** 如果子节点是普通节点 找到父级 将子节点追加到父级中*/if (fiber.tag === "host_component") {
parentFiber.stateNode.appendChild(fiber.stateNode)}}})/*** 备份旧的 fiber 节点对象*/fiber.stateNode.__rootFiberContainer = fiber
}const workLoop = deadline => {
/*** 如果子任务不存在 就去获取子任务*/if (!subTask) {
subTask = getFirstTask()}/*** 如果任务存在并且浏览器有空余时间就调用* executeTask 方法执行任务 接受任务 返回新的任务*/while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)}if (pendingCommit) {
commitAllWork(pendingCommit)}
}
渲染类组件
react下创建Component文件夹index.js
import {
scheduleUpdate } from "../reconciliation"export class Component {
constructor(props) {
this.props = props}setState(partialState) {
scheduleUpdate(this, partialState)}
}
在getTag下找到如果是类组件时,导入处理类组件
import {
Component } from "../../Component"const getTag = vdom => {
if (typeof vdom.type === "string") {
return "host_component"} else if (Object.getPrototypeOf(vdom.type) === Component) {
return "class_component"} else {
return "function_component"}
}
export default getTag
处理stateNode属性,在createStateNode下处理
import {
createDOMElement } from "../../DOM"
import {
createReactInstance } from "../createReactInstance"const createStateNode = fiber => {
if (fiber.tag === "host_component") {
return createDOMElement(fiber)} else {
return createReactInstance(fiber)}
}export default createStateNode
类组件创建fiber.type
export const createReactInstance = fiber => {
let instance = nullif (fiber.tag === "class_component") {
instance = new fiber.type(fiber.props)} else {
instance = fiber.type}return instance
}
如果是类组件,不能直接提交,需要通过while循环,找到他父级的父级,就是普通节点,通过reconcileChildren方法处理
处理函数组件
在executeTask方法
// 接收的fiber其实就是subTask,最外层节点,需要拿到子节点的virtual DOM对象
// 子节点通过 fiber.props.children来获取
const executeTask = fiber => {
/*** 构建子级fiber对象*/if (fiber.tag === "class_component") {
if (fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) {
fiber.stateNode.state = {
...fiber.stateNode.state,...fiber.stateNode.__fiber.partialState}}reconcileChildren(fiber, fiber.stateNode.render())} else if (fiber.tag === "function_component") {
reconcileChildren(fiber, fiber.stateNode(fiber.props))} else {
reconcileChildren(fiber, fiber.props.children)}/*** 如果子级存在 返回子级* 将这个子级当做父级 构建这个父级下的子级*/if (fiber.child) {
return fiber.child}/*** 如果存在同级 返回同级 构建同级的子级* 如果同级不存在 返回到父级 看父级是否有同级*/let currentExecutelyFiber = fiberwhile (currentExecutelyFiber.parent) {
currentExecutelyFiber.parent.effects = currentExecutelyFiber.parent.effects.concat(currentExecutelyFiber.effects.concat([currentExecutelyFiber]))if (currentExecutelyFiber.sibling) {
return currentExecutelyFiber.sibling}currentExecutelyFiber = currentExecutelyFiber.parent}pendingCommit = currentExecutelyFiber
}
在 reconcileChildren 接收传递的函数组件值和属性
实现节点的更新操作
当dom渲染之后,要保存旧的节点对象,在创建新的Fiber节点对象,在创建新的节点,要看旧的存不存在,存在执行更新操作,执行更新的操作对象
在commitAllWork中找到对应的节点对象
const commitAllWork = fiber => {
/*** 循环 effets 数组 构建 DOM 节点树*/fiber.effects.forEach(item => {
if (item.tag === "class_component") {
item.stateNode.__fiber = item}if (item.effectTag === "delete") {
item.parent.stateNode.removeChild(item.stateNode)} else if (item.effectTag === "update") {
/*** 更新*/if (item.type === item.alternate.type) {
/*** 节点类型相同*/updateNodeElement(item.stateNode, item, item.alternate)} else {
/*** 节点类型不同*/item.parent.stateNode.replaceChild(item.stateNode,item.alternate.stateNode)}} else if (item.effectTag === "placement") {
/*** 向页面中追加节点*//*** 当前要追加的子节点*/let fiber = item/*** 当前要追加的子节点的父级*/let parentFiber = item.parent/*** 找到普通节点父级 排除组件父级* 因为组件父级是不能直接追加真实DOM节点的*/while (parentFiber.tag === "class_component" ||parentFiber.tag === "function_component") {
parentFiber = parentFiber.parent}/*** 如果子节点是普通节点 找到父级 将子节点追加到父级中*/if (fiber.tag === "host_component") {
parentFiber.stateNode.appendChild(fiber.stateNode)}}})/*** 备份旧的 fiber 节点对象*/fiber.stateNode.__rootFiberContainer = fiber
}
更新根节点对象的地方,就是getFirstTask方法的alternate
const getFirstTask = () => {
/*** 从任务队列中获取任务*/const task = taskQueue.pop()if (task.from === "class_component") {
const root = getRoot(task.instance)task.instance.__fiber.partialState = task.partialStatereturn {
props: root.props,stateNode: root.stateNode,tag: "host_root",effects: [],child: null,alternate: root}}/*** 返回最外层节点的fiber对象*/return {
props: task.props,stateNode: task.dom,tag: "host_root",effects: [],child: null,alternate: task.dom.__rootFiberContainer}}
在DOM下的updateNodeElement.js中处理
export default function updateNodeElement(newElement,virtualDOM,oldVirtualDOM = {
}
) {
// 获取节点对应的属性对象const newProps = virtualDOM.props || {
}const oldProps = oldVirtualDOM.props || {
}if (virtualDOM.type === "text") {
if (newProps.textContent !== oldProps.textContent) {
if (virtualDOM.parent.type !== oldVirtualDOM.parent.type) {
virtualDOM.parent.stateNode.appendChild(document.createTextNode(newProps.textContent))} else {
virtualDOM.parent.stateNode.replaceChild(document.createTextNode(newProps.textContent),oldVirtualDOM.stateNode)}}return}Object.keys(newProps).forEach(propName => {
// 获取属性值const newPropsValue = newProps[propName]const oldPropsValue = oldProps[propName]if (newPropsValue !== oldPropsValue) {
// 判断属性是否是否事件属性 onClick -> clickif (propName.slice(0, 2) === "on") {
// 事件名称const eventName = propName.toLowerCase().slice(2)// 为元素添加事件newElement.addEventListener(eventName, newPropsValue)// 删除原有的事件的事件处理函数if (oldPropsValue) {
newElement.removeEventListener(eventName, oldPropsValue)}} else if (propName === "value" || propName === "checked") {
newElement[propName] = newPropsValue} else if (propName !== "children") {
if (propName === "className") {
newElement.setAttribute("class", newPropsValue)} else {
newElement.setAttribute(propName, newPropsValue)}}}})// 判断属性被删除的情况Object.keys(oldProps).forEach(propName => {
const newPropsValue = newProps[propName]const oldPropsValue = oldProps[propName]if (!newPropsValue) {
// 属性被删除了if (propName.slice(0, 2) === "on") {
const eventName = propName.toLowerCase().slice(2)newElement.removeEventListener(eventName, oldPropsValue)} else if (propName !== "children") {
newElement.removeAttribute(propName)}}})
}