React 源码分析

工程化配置

momorepo 简介

由于我们React库中有React-dom库,React-native库等等等,为了管理这些工程文件库可以使用momorepo来进行版本控制和管理

momorepo: 一种项目库的管理方式 可以很方便的协同管理不同独立的库的生命周期

  • 一些简单的工具:npm-workspace、Yarn-workspace、pnpm-workspace
  • 专业工具:nx、bit、turborepo、rush、lerna

包管理器

选择 pnpm 作为我们项目的包管理器

  • 安装依赖更快
  • 解决了 npm 的一些疑难问题

pnpm 初始化

1
2
npm install -g pnpm
pnpm init

pnpm 实验 momorepo:建立 pnpm-workspace.yaml文件去书写配置

1
2
3
4
5
6
7
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
# all packages in subdirs of components/
- 'components/**'
# exclude packages that are inside test directories
- '!**/test/**'

定义开发规范

代码规范检查和修复

  • 代码规范:lint 工具
    安装:

    1
    pnpm i eslint -D -w

    初始化:

    1
    npx eslint --init

    我们只需要 eslint 给我们代码检查 而代码格式我们使用prettier来做

    .eslintrc.json 配置如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    {
    "env": {
    "browser": true,
    "es2021": true,
    "node": true
    },
    // 打开和关闭
    "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
    "plugin:prettier/recommended"
    ],
    // 解析器 AST语法树
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
    },
    "plugins": ["@typescript-eslint", "prettier"],
    "rules": {
    "prettier/prettier": "error",
    "no-case-declarations": "off",
    // 允许any
    "@typescript-eslint/no-explicit-any": "off"
    }
    }
  • 代码风格:prettier
    安装

    1
    pnpm i prettier -D -w

    新建.prettierrc.json 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "useTabs": false,
    "tabWidth": 2,
    "jsxSingleQuote": false,
    "singleQuote": true,
    "endOfLine": "auto",
    "semi": false,
    "trailingComma": "none"
    }
  • 将 prettier 集成到 eslint 中
    eslint-config-prettier:覆盖 ESlint 本身的规则配置
    eslint-plugin-prettier:用 Preiiter 来修复代码
    安装

    1
    npm i eslint-config-prettier eslint-plugin-prettier -D -w

    为 lint 增加对应的执行脚本、并验证效果

    1
    "lint": "eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./packages"

Commit 规范

安装husky用于拦截 commit 命令

1
pnpm i -D -w husky

初始化 husky

1
npx husky isntall

将刚才执行的pnpm lint 纳入 commit 时将 husky 将执行的脚本

1
npx husky add .husky/pre-commit "pnpm lint"

通过commitlint对 git 提交信息进行检查 首先安装必要的库

1
pnpm i -D -w @commitlint/cli @commitlint/config-conventional -D -w

新建配置文件.commitlintrc.js

1
2
3
module.exports = {
extends: ['@commitlint/config-conventional']
}

集成到 husky 中

1
npx husky add .husky/commit-msg "npx --no-install commitlint -e $HUSKY_GIT_PARAMS"

conventional 规规集意义

1
2
// 提交的类型:摘要信息
<type>: <subject>

常见的type值

  • feat: 添加新功能
  • fix: 修复 bug
  • chore: 一些不影响功能的更改
  • docs: 文档的修改
  • perf:性能方面的优化
  • refactor: 代码重构
  • test: 添加一些测试代码

配置 tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"compileOnSave": true,
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": false,
"skipLibCheck": true,
// 基础路径
"baseUrl": "./packages"
},
// "include": ["packages/index.ts"],
"exclude": ["node_modules", "packages/dist/**/*"]
}

选择构建工具 rollup

  • 我们项目是库 而不是业务代码
  • 希望工具尽可能简洁 打包产物可读性更高

安装 rollup

1
pnpm i -D -w rollup

下面我们先写一段代码在看如何配置打包工具

JSX 转换

React 中项目结构:

  • react (宿主环境无关的公用方法)
  • react-reconciler (协调器的实现 宿主环境无关)
  • 各种宿主环境的包
  • shared (公用辅助方法 宿主环境无关)

JSX 转换是什么

包括两部分:

  • 编译时:(babel 有专门的 parser 去解析 jsx 语言拓展)
  • 运行时:jsx 方法或 React.createElement 方法的实现(包括 dev、prod)

编译时由 babel 编译实现 我们只用实现运行时 工作量包括

  • 实现 jsx 方法
  • 实现打包流程
  • React.createElement 方法

React 中的 JSX 初步实现

首先将 packages/* 目录下面分成两个工程 reactshared并对应初始化,shared 下面是一些共享的方法和函数 react 中需要引入 shared 则要在 package.json 中存在安装

1
2
3
4
5
{
"dependencies": {
"shared": "workspace:*"
}
}

react/index.ts

1
2
3
4
5
import { jsxDEV } from './src/jsx'
export default {
version: '0.0.0',
createElement: jsxDEV
}

react/src/jsx.ts中实现 jsx 的具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'
import type {
Type,
Key,
Ref,
Props,
ElementType,
ReactElementType
} from 'shared/ReactTypes'

export const ReactElement = function (
type: Type,
key: Key,
ref: Ref,
props: Props
): ReactElementType {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
__mark: 'yueyun'
}
return element
}
export const jsxDEV = function (
type: ElementType,
config: Props
// ...maybeChildren: any[]
) {
let key: Key = null
let ref: Ref = null
const props: Props = {}
// 1. 处理 props 遍历
for (const propName in config) {
const val = config[propName]
if (propName === 'key') {
if (val !== undefined) {
key = '' + val
}
continue
}
if (propName === 'ref') {
if (val !== undefined) {
ref = val
}
continue
}
// 不为原型链上的属性
if ({}.hasOwnProperty.call(config, propName)) {
props[propName] = val
}
}
// const childrenLength = maybeChildren.length
// if (childrenLength) {
// if (childrenLength === 1) {
// props.children = maybeChildren[0]
// } else {
// props.children = maybeChildren
// }
// }
return ReactElement(type, key, ref, props)
}

shared/ReactTypes.ts中定义

1
2
3
4
5
6
7
8
9
10
11
12
13
export type Type = any
export type Props = any
export type Key = any
export type Ref = any
export type ElementType = any
export interface ReactElementType {
$$typeof: symbol | number
type: ElementType
key: Key | null
ref: Ref | null
props: Props
__mark: string
}

ReactSybols.ts中定义

1
2
3
4
5
const supporSymbol = typeof Symbol === 'function' && Symbol.for

export const REACT_ELEMENT_TYPE = supporSymbol
? Symbol.for('react.element')
: 0xeac7

实现打包流程

实现 jsx 方法

  • jsxDEV 方法(dev 环境)
  • jsx 方法 (prod 环境)
  • React.createElement 方法

对应上述方法打包文件

  • react/jsx-dev-runtime.js (dev 环境)
  • react/jsx-runtime.js (prod 环境)
  • React

现在项目根目录下面新建scripts文件夹

新建工具scripts/utils.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import path from 'path'
import fs from 'fs'
import ts from 'rollup-plugin-typescritp2'
import cjs from '@rollup/plugin-commonjs'
const pkgPath = path.resolve(__dirname, '../../packages')
const distPath = path.resole(__dirname, '../../dist/node_modules')

/**
* 获取包路径
* @param {string} pakName 包名
* @param {boolean} isDist 是否是dist目录
* @returns {string} 包路径
* @author yueyun
*/
export function resolvePkgPath(pakName, isDist) {
return isDist ? `${distPath}/${pakName}` : `${pkgPath}/${pakName}`
}

/**
* 获取包的package.json
* @param {string} pkgName 包名
* @returns {object} package.json对象
* @author yueyun
* @example
* const pkg = getPackageJSON('utils')
* console.log(pkg.name) // @yueyun/utils
* console.log(pkg.version) // 1.0.0
* */
export function getPackageJSON(pkgName) {
// 包路径
const path = `${resolvePkgPath(pkgName)}/package.json`
const str = fs.readFileSync(path, { encoding: 'utf-8' })
return JSON.parse(str)
}

/**
* 提供rollup的基础插件
* @param {object} options 配置项
* @param {object} options.typescript typescript配置项
* @returns {array} 插件数组
* @author yueyun
* @apprehen
* */
export function getBaseRollupPlugins({ typescript = {} } = {}) {
return [ts(typescript), cjs()]
}

新建scripts/react.config.js去处理 react 库的打包情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { getPackageJSON, resolvePkgPath, getBaseRollupPlugins } from './utils'
import generatePackageJson from 'rollup-plugin-generate-package-json'
const { name, module } = getPackageJSON('react')
const pkgPath = resolvePkgPath(name)
const pkgDistPath = resolvePkgPath(name, true)
export default [
// react
{
input: `${pkgPath}/${module}`,
output: {
file: `${pkgDistPath}/index.js`,
name: 'index.js',
format: 'umd'
},
plugins: [
...getBaseRollupPlugins({}),
generatePackageJSON({
inputFolder: pkgPath,
outputFolder: pkgDistPath,
baseContents: ({ name, description, version }) => ({
name,
description,
version,
main: 'index.js'
})
})
]
},
{
input: `${pkgPath}/src/jsx.ts`,
output: [
// jsx-runtime
{
file: `${pkgDistPath}/jsx-runtime.js`,
name: 'jsx-runtime.js',
format: 'umd'
},
// jax-dev-runtime
{
file: `${pkgDistPath}/jsx-dev-runtime.js`,
name: 'jsx-dev-runtime.js',
format: 'umd'
}
],
plugins: getBaseRollupPlugins({})
}
]

在根目录的package.json下面添加打包的命令

1
2
3
4
5
"scripts": {
"clean": "rm -rf ./dist",
"lint": "eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./packages",
"build:dev": "pnpm run clean && rollup --bundleConfigAsCjs --config scripts/rollup/react.config.js"
},

打包后的文件目录如下所示

调式打包结果

Pnpm link 文档

Reconciler

reconciler 是 React 核心逻辑所在的模块、意思是协调器、协调

Reconciler 有什么用

jQuery工作原理(过程驱动):

前端框架结构与工作原理(状态驱动):

  • 消费 JSX
  • 没有编译优化
  • 开放通用 API 供不同的宿主环境使用

核心模块消费 JSX 的过程

核心模块操作的数据结构

当前已知的数据结构:ReactElement

React Element 如果作为核心模块操作的数据结构存在的问题:

  • 无法表达节点之间的关系
  • 字段有限不能更容易的拓展(比如无法表达状态)

所以需要一种新的数据结构 它的特点如下:

  • 介于 React Element 与真实 UI 节点之间
  • 能够表达节点之间的关系
  • 方便拓展 (不仅能作为数据存储单元,也能作为工作单元)

这就是FiberNode(虚拟DOM在 React 中的实现)

当前了解的节点类型:

  • JSX
  • React Element
  • FiberNode
  • DOM Element

reconciler 的工作方式

对于同一个节点,比较其ReactElementfiberNode,生成子fiberNode 并根据比较的结果生成不同的标记(插入、删除、移动…..),对应不同宿主环境 API的执行

比如 挂载<div></div>

1
2
3
4
5
6
7
// React Element <div></div>
jsx('div')
// 对应fiberNode
null
// 生成子fiberNode
// 对应标记
Placement

<div></div>更新为<p></p>

1
2
3
4
5
6
7
// React Element <p></p>
jsx("p")
// 对应的fiberNode为
FiberNode(type: "div")
// 生成子fiberNode
// 对应标记
Deletion Placement

当所有的 React Element 比较完全后 会生成一个 fiberNode 树 一共会存在两颗 fiberNode 树:

  • current:与视图中真实 UI 对应的 fiberNode
  • workInProgress:触发更新后、正在 reconciler 中计算的 fiberNode 树

JSX 消费顺序

DFS 深度优先遍历与 BFS 广度优先遍历详解

以 DFS(深度优先遍历)的顺序遍历React Element 这意味着

  • 如果有子节点,遍历子节点

  • 如果没有子节点,遍历兄弟节点

    1
    2
    3
    4
    <Card>
    <h3>你好</h3>
    <p>MyReact</p>
    </Card>

这是一个递归的过程、存在递、归两个阶段:

  • 递:对应 beginWork
  • 归:对应 completeWork

我们新建一个packages/react-reconciler包去实现协调器

建立packages/react-reconciler/filber.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import type { Props, Key, Ref } from 'shared/ReactTypes'
import { WorkTag } from './workTags'
import { Flags, NoFlags } from './fiberFlags'
export class FiberNode {
// 元素类型
type: any
// 代表fiber的类型
tag: WorkTag
// 代表fiber属性
pendingProps: Props
key: Key
// 表示fiber对应的真实DOM节点
stateNode: any
// 指向父fiberNode
return: FiberNode | null
// 指向兄弟fiberNode
sibling: FiberNode | null
// 指向子fiberNode
child: FiberNode | null
// 表示当前fiber在父fiber中的位置
index: number
ref: Ref
// 表示fiber的属性
memoizedProps: Props | null
// 表示fiber的上一次渲染的fiber
alternate: FiberNode | null
// 表示fiber的状态
flags: Flags
constructor(tag: WorkTag, pendingProps: Props, key: Key) {
// 实例属性
this.tag = tag
this.key = key
// HostComponent <div> div DOM
this.stateNode = null
// FunctionComponent ()=>{}
this.type = null
// 构成树形结构
this.return = null
this.sibling = null
this.child = null
this.index = null
this.ref = null
// 作为工作单元
this.pendingProps = pendingProps
this.memoizedProps = null
// 缓冲树
this.alternate = null
this.flags = NoFlags
}
}

建立fiberFlags.ts

1
2
3
4
5
export type Flags = number
export const NoFlags = 0b0000001
export const Placement = 0b0000010
export const Update = 0b0000100
export const ChildDeletion = 0b0001000

建立workTags.ts

1
2
3
4
5
6
7
8
9
export type WorkTag =
| typeof FunctionComponent
| typeof HostRoot
| typeof HostComponent
| typeof HostText
export const FunctionComponent = 0
export const HostRoot = 3
export const HostComponent = 5
export const HostText = 6

现在定义工作函数

建立src/beginWork.ts

1
2
3
4
5
// 递归中的递阶段
import { FiberNode } from './fiber'
export const beginWork = (fiber: FiberNode) => {
console.log('beginWork', fiber)
}

建立src/completeWork.ts

1
2
3
4
import { FiberNode } from './fiber'
export const completeWork = (fiber: FiberNode) => {
console.log('completeWork', fiber)
}

建立src/workLoop.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { FiberNode } from './fiber'
import { completeWork } from './completeWork'
import { beginWork } from './beginWork'

let workInProgress: FiberNode | null = null
/**
* @param {FiberNode} fiber
* @description 初始化
* @returns {void}
* @example
* @author yueyun
* */
function prepareFreshStatck(fiber: FiberNode) {
workInProgress = fiber
}

/**
* @param root {FiberNode} 根节点
* @description 渲染根节点
* @returns {void}
* @example
* renderRoot(root)
* @author yueyun
*/
function renderRoot(root: FiberNode) {
prepareFreshStatck(root)
do {
try {
workLoop()
break
} catch (err) {
console.warn('workLoop发生错误', err)
workInProgress = null
}
} while (true)
}

/**
* @param {void}
* @description 通过workInProgress遍历fiber树 处理当前的工作单元
* @returns {void}
* */
function workLoop() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}

/**
* @param {FiberNode} fiber 当前的工作单元
* @description 处理当前的工作单元
* @returns {void}
* @author yueyun
*/
function performUnitOfWork(fiber: FiberNode) {
const next = beginWork(fiber)
//状态更新和渲染 memoizedProps该 Fiber 节点上次渲染时使用的 props。它是“记忆化”的 props,用于比较和避免不必要的重渲染。
//pendingProps 这个属性存储了即将用于渲染的新 props。当组件接收到新的 props 时,这些新的 props 首先被设置到 pendingProps 上。
fiber.memoizedProps = fiber.pendingProps
if (next == null) {
//递归向上回溯 直到遍历完整个 Fiber 树或者达到树的顶端
completeUnitOfWork(fiber)
} else {
workInProgress = next
}
}

/**
* @param {FiberNode} fiber 当前的工作单元
* @description 递归向上回溯 直到遍历完整个 Fiber 树或者达到树的顶端
* @returns {void}
* @author yueyun
* */
function completeUnitOfWork(fiber: FiberNode) {
let node: FiberNode | null = fiber
do {
//
completeWork(node)
const sibling = node.sibling
if (sibling !== null) {
workInProgress = sibling
return
}
node = node.return
workInProgress = node
} while (node !== null)
}

如何触发更新

常见的触发更新的方式:

  • ReactDom.createRoot().render (或者老版的ReactDom.render)
  • this.setState
  • useState中的dispatch方法

我们希望实现一套同一的更新机制 他们的特点是

  • 兼容上述触发更新的方式
  • 方便后续扩展(优先级机制…)

更新机制的组成部分

  • 代表更新的数据结构 – Update
  • 消费 Update 的数据结构 – UpdateQueue

现在先简单的实现一个 update 的机制

新建src/updateQueue.ts

Action的类型定义

1
export type Action<State> = State | ((prevState: State) => State)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { Action } from 'shared/ReactTypes'

/**
*Update数据结构
* @param action function | any
* @returns {Update}
* @description 用于更新状态的数据结构接口
* */
export interface Update<State> {
action: Action<State>
}

/**
* UpdateQueue数据结构
* @param shared { pending: Update<State> | null }
* @returns {UpdateQueue}
* @description 用于存储待处理的更新
*/
export interface UpdateQueue<State> {
shared: {
pending: Update<State> | null
}
}

/**
*@param Action<State> function | any
*@returns {Update<State>}
*@description 泛型函数接受一个Action<State>类型的参数,返回一个Update<State>类型的对象用于创建一个新的更新
*/
export const createUpdate = <State>(action: Action<State>): Update<State> => {
return {
action
}
}

/**
*@returns {UpdateQueue<Action>}
*@description 创建一个新的更新队列 被初始化为一个为null
*/
export const createUpdateQueue = <Action>() => {
return {
shared: {
pending: null
}
} as UpdateQueue<Action>
}

/**
*@param updateQueue UpdateQueue<Action>
*@param update Update<Action>
*@returns {void}
*@description 将更新添加到更新队列中
*/
export const enqueueUpdate = <Action>(
updateQueue: UpdateQueue<Action>,
update: Update<Action>
) => {
updateQueue.shared.pending = update
}

/**
* @param baseState State 基础状态
* @param pendingUpdate Update<State> | null 待处理的更新
* @returns {State} memoizedState
* @description 泛型函数处理更新队列 接受一个基础状态和可能的更新 返回更新后的状态
*/
export const processUpdateQueue = <State>(
baseState: State,
pendingUpdate: Update<State> | null
): { memoizedState: State } => {
const result: ReturnType<typeof processUpdateQueue<State>> = {
memorizedState: baseState
}
if (pendingUpdate !== null) {
const action = pendingUpdate.action
if (action instanceof Function) {
result.memoizedState = action(baseState)
} else {
result.memoizedState = action
}
}
return result
}
  • 实现mount时调用的 API
  • 将该 API 接入上述更新机制中

需要考虑的事情:

  • 更新可能发生于任意的组件,而更新流程是从根节点递归的
  • 需要一个统一的根节点保存通用信息
1
ReactDom.createRoot(rootElement).render(<App />)

首先实现FiberRootNode的数据结构 在fiber.ts中继续添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import type { Container } from 'hostConfig'
/**
* @param {Container} container 宿主容器
* @param {FiberNode} hostRootFiber 根节点
* @description FiberRootNode数据结构
* 包含的数据
* @argument container: 宿主容器
* @argument current: 当前的工作单元
* @argument finishiedWork: 已经完成的工作单元
* @returns {FiberRootNode}
*/
export class FiberRootNode {
container: Container
current: FiberNode
finishiedWork: FiberNode | null
constructor(container: Container, hostRootFiber: FiberNode) {
this.current = hostRootFiber
this.container = container
hostRootFiber.stateNode = this
this.finishiedWork = null
}
}

新建src/fiberReconciler.ts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { HostRoot } from './workTags'
import { Container } from 'hostConfig'
import { FiberNode, FiberRootNode } from './fiber'
import {
UpdateQueue,
createUpdate,
createUpdateQueue,
enqueueUpdate
} from './updateQueue'
import { ReactElementType } from 'shared/ReactTypes'
import { scheduleUpdateOnFiber } from './workLoop'
/**
* @param {Container} container 容器
* @description 创建容器
* @returns {FiberRootNode}
*/
export function createContainer(container: Container) {
const hostRoot = new FiberNode(HostRoot, null, null)
const root = new FiberRootNode(container, hostRoot)
hostRoot.updateQueue = createUpdateQueue
return root
}

/**
* @param {ReactElementType | null} element 用于更新的React元素
* @param {FiberRootNode} root FiberRootNode 根节点
* @description 使用新的react元素更新容器
* @returns {ReactElementType | null} 返回更新后的React元素
* */
export function updateContainer(
element: ReactElementType | null,
root: FiberRootNode
) {
const hostRootFiber = root.current
const update = createUpdate<ReactElementType | null>(element)
// 将更添加到更新队列中
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
)
// 调度更新
scheduleUpdateOnFiber(hostRootFiber)
return element
}

修改workLoop.ts中的部分内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createWorkInProgress } from './fiber'
function prepareFreshStack(root: FiberRootNode) {
workInProgress = createWorkInProgress(root.current, {})
}

/**
* @param {FiberNode} fiber
* @description 调度更新
*/
export function scheduleUpdateOnFiber(fiber: FiberNode) {
// Todo 调度功能
console.log('scheduleUpdateOnFiber', fiber)
const root = makeUpdateFromFiberToRoot(fiber)
renderRoot(root)
}

/**
* @param {FiberNode} fiber
* @description 从Fiber节点到根节点
* @returns {FiberRootNode | null} 返回根节点
*/
export function makeUpdateFromFiberToRoot(fiber: FiberNode) {
console.log('makeUpdateFromFiberToRoot', fiber)
let node = fiber
let parent = fiber.return
if (parent !== null) {
node = parent
parent = node.return
}
if (node.tag === HostRoot) {
return node.stateNode
}
return null
}

fiber.ts中新增加createWorkInProgress函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

/**
* @param {FiberNode} current 当前的工作单元
* @param {Props} pendingProps 表示fiber的属性
* @description 创建一个新的工作单元
* @returns {FiberNode}
* wip: work in progress 工作单元 用于更新的数据结构 wip是current的备份 双缓冲技术
* */
export const createWorkInProgress = (
current: FiberNode,
pendingProps: Props
):FiberNode => {
let wip = current.alternate
if (wip === null) {
// mount
wip = new FiberNode(current.tag, pendingProps, current.key)
wip.type = current.type
wip.stateNode = current.stateNode
wip.alternate = current
current.alternate = wip
} esle {
// update
wip.pendingProps = pendingProps
wip.flags = NoFlags
wip.updateQueue = current.updateQueue
wip.child = current.child
wip.memoizedProps = current.memoizedProps
wip.memoizedState = current.memoizedState
}
return wip
}