一、概述
在React内部,会将所有的React元素转换为Filber树 Filber树总体结构图示
在这张图里,出现了filberRootNode,其他的节点都是FilberNode类型,hostRootFilber就是最外层的FilberNode。
在项目里面,当我们调用ReactDOM.createRoot(root)方法的时候,就会创建出上面的FilberRootNode和hostRootFilber。并且将二者之间的指向关系确定。
二、FilberNode
FilberNode 是 React 中用于表示虚拟 DOM 树中节点的基本数据结构。它包含了丰富的信息:
(1)type
type:可以是字符串(如 'div'、'span' 等表示 HTML 标签类型)、函数(代表自定义组件)或者其他特定类型(如 React.Fragment),用于标识节点的类型。
(2)tag
在 FilberNode 结构里,tag
属性主要用于标识节点的类型。这个类型可以是多种情况。
对于原生 HTML 元素,tag
属性的值就是 HTML 标签名。例如,如果 FilberNode 代表一个<div>
元素,那么tag
的值就是"div"
;如果是一个<span>
元素,tag
就是"span"
。这使得 React 在渲染阶段能够根据这个标签名创建对应的真实 DOM 元素。
对于自定义组件,tag
属性存储的是组件的引用(可以是函数组件或者类组件的引用)。比如,有一个自定义函数组件MyComponent
,在 FilberNode 表示这个组件时,tag
属性的值就是MyComponent
这个函数的引用。
js
const FunctionComponent = 'FunctionComponent'; //对应函数
const HostRoot= 'HostRoot'; //对应hostRootFilber
const HostComponent= 'HostComponent'; //对应div
const HostText = 'HostText'; //对应文本节点
(3)key
key:在列表渲染等场景中用于优化渲染和复用节点。它是一个唯一标识符,帮助 React 确定在更新过程中节点是否发生了变化,对应的就是ReactElement中的key。
(4)ref
ref:用于获取对真实 DOM 节点的引用,方便开发者在特定情况下与 DOM 进行交互,对应的就是ReactElement中的ref。
(5)stateNode
stateNode :stateNode
属性主要用于存储与该虚拟节点对应的真实 DOM 节点。这意味着在虚拟 DOM 的世界里,每一个 FilberNode 都通过stateNode
属性知道自己在真实浏览器环境中的 "物理存在"。例如,当一个 FilberNode 代表一个<div>
标签时,stateNode
就是浏览器内存中对应的真实<div>
元素。
(6)alternate
alternate :alternate
属性用于存储与当前 FilberNode 对应的另一个版本的 FilberNode,这个另一个版本通常是在更新过程中创建的新节点。例如,当一个组件的状态发生变化,React 会构建一个新的 FilberNode 树来反映这个变化,旧的 FilberNode 和新的 FilberNode 通过alternate
属性相互关联。
(7)pendingProps
在 FilberNode(React 虚拟 DOM 节点)的体系中,pendingProps
是一个非常重要的属性。它存储了即将应用到该节点的属性(properties)。这些属性包含了各种信息,例如用于配置 HTML 元素的属性(如class
、style
、id
等)或者传递给自定义组件的props
。
可以把pendingProps
看作是一个 "待办事项" 列表,它记录了在下一次渲染阶段或者更新阶段需要应用到节点的所有属性相关的信息。
(8)memoizedProps
在 FilberNode 的结构中,memoizedProps
用于存储上一次渲染时该节点所使用的属性(props
)。它是 React 内部用于优化组件重新渲染的一个重要属性。
主要用于和pendingProps
(即将应用到节点的属性)进行对比。通过这种对比,React 可以快速判断出属性是否发生了变化,从而决定是否需要重新渲染该节点及其子节点。这是一种性能优化策略,避免了不必要的 DOM 操作和组件重新渲染。
(9)memoizedState
在 React 组件的运行机制中,memoizedState
是一个关键属性,尤其在类组件和部分基于 React 内部状态管理的函数组件场景下发挥重要作用。它主要用于存储组件已经记忆(memoized)的状态。
对于类组件而言,这个属性通常是在组件实例内部维护的,用来记录组件状态相关的信息,例如this.state
的内容在内部可能会被存储在memoizedState
中,以便在组件的生命周期内进行状态的管理和更新。
在函数组件中,当使用一些 React 的高级特性(如 Hooks)时,memoizedState
也会参与到状态的保存和更新过程中。
(10)return
在 FilberNode的结构中,return
属性用于指向该节点在更新过程中的 "返回" 节点。这个概念在 React 的协调(reconciliation)过程中比较重要,特别是在处理复杂的组件更新和复用场景时。
例如,当一个组件的一部分被更新,而这部分组件对应的 FilberNode 在更新后需要 "返回" 到某个父节点或者其他关联节点来完成更新流程,return
属性就起到了这个指向作用。
在 React 的更新算法中,return
属性帮助确定节点在更新后的挂载位置或者关联位置。假设一个 FilberNode 代表一个子组件,在更新过程中,如果这个子组件被移动到了一个新的位置或者被重新挂载,return
属性可以确保它能够正确地与新的父节点或者周围的节点建立联系。这对于保持虚拟 DOM 树的结构完整性和更新的准确性非常重要。
(11)child
child
属性指向该 FilberNode 的第一个子 FilberNode,它是构建虚拟 DOM 树的关键属性。通过这个属性,可以将多个 FilberNode 连接成一个树状结构,其中每个节点可以有自己的子节点,从而形成复杂的层次关系。
例如,对于一个代表<div>
元素的 FilberNode,如果它包含一个<p>
元素作为子元素,那么这个<div>
元素对应的 FilberNode 的child
属性就会指向代表<p>
元素的 FilberNode。
(12)sibling
sibling
属性用于指向同一层级的下一个 FilberNode(兄弟节点)。在虚拟 DOM 树的构建和遍历过程中,这个属性帮助建立起同一层级节点之间的顺序关系。
比如,在一个包含多个并列元素(如多个<li>
元素在一个<ul>
中)的组件中,每个代表<li>
元素的 FilberNode 会通过sibling
属性连接到下一个<li>
元素对应的 FilberNode,从而形成一个链表结构来表示同一层级的节点顺序。
(13)index
index
属性主要用于在节点列表(如数组渲染的组件列表)中标识节点的位置。它是一个数字,用于表示一个 FilberNode 在其兄弟节点列表中的索引位置。
例如,在一个通过map
函数渲染的列表组件中,每个代表列表项的 FilberNode 会有一个index
属性,用于区分不同的列表项,并且在更新过程中帮助 React 确定节点的相对位置。
在更新列表组件时,index
属性对于确定节点是否被移动、添加或者删除非常重要。React 可以通过比较新旧节点的index
属性来判断节点的位置变化情况。
同时,index
属性在节点复用策略中也起到关键作用。例如,在shouldComponentUpdate
或者React.memo
的优化策略中,index
属性可以作为一个参考因素,帮助判断节点是否可以复用,尤其是在处理列表组件的更新时。
三、FilberRootNode
FilberRootNode 是 React 应用中虚拟 DOM 树的根节点相关的数据结构。它在 React 的渲染和更新机制中扮演着至关重要的角色,是整个应用虚拟 DOM 架构的起始点。
(1)current
- 含义 :
current
是 FilberRootNode 中一个关键的属性,它指向当前正在被渲染和使用的 FilberNode 树的根节点。这个 FilberNode 树代表了当前用户界面(UI)的状态。 - 作用 :在 React 应用的运行过程中,当有更新发生时,React 会构建一个新的 FilberNode 树来反映更新后的状态。在合适的时机,
current
属性会被更新,从指向旧的 FilberNode 树切换到指向新的 FilberNode 树,从而实现 UI 的无缝更新。例如,当一个组件的状态改变导致整个 UI 需要重新渲染时,React 会创建新的 FilberNode 树,然后将 FilberRootNode 的current
指向这个新树,完成 UI 从旧状态到新状态的过渡。
(2)containerInfo
- 含义 :
containerInfo
属性包含了与挂载真实 DOM 相关的信息。它确定了整个 React 应用在真实 DOM 中的挂载位置,即告诉 React 应该将虚拟 DOM 挂载到哪个真实的 HTML 元素上。 - 作用 :在 React 应用的初始挂载阶段,
containerInfo
发挥着关键作用。例如,通过ReactDOM.render
方法将一个 React 应用挂载到一个id
为app
的 HTML 元素上时,containerInfo
就会保存与这个id
为app
的元素相关的信息,使得 React 能够将虚拟 DOM 树转换为真实 DOM 并挂载到该元素内部,从而在浏览器中呈现出应用的界面。
(3)finishWork
在 React 的渲染和更新机制中,finishWork
是一个关键的内部处理阶段相关概念。它主要涉及到对已经完成构建或者更新的 FilberNode(虚拟 DOM 节点)的最终处理工作。
这个阶段在整个渲染或更新流程中处于较后的位置,在 FilberNode 的创建、属性更新、子节点处理等操作基本完成之后,finishWork
开始对这些节点进行整合、挂载真实 DOM(如果需要)或者其他收尾性质的操作。
四、定义ReactDOM
有了上面FilberNode和FilberRootNode的数据结构,我们可以实现FilberRootNode类和FilberNode类
js
export class FilberRootNode {
constructor(container, hostRootFiber) {
this.container = container
this.current = hostRootFiber
hostRootFiber.stateNode = this
this.finishedWork = null
}
}
js
export class FilberNode {
constructor(tag, pendingProps, key) {
this.tag = tag;
this.key = key;
this.stateNode = null;
this.type = null;
this.return = null;
this.sibling = null;
this.child = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.memoizedState = null;
this.alternate = null;
}
}
在react项目中,我们是这样使用的ReactDOM的
js
const root = document.getElementById('root')
ReactDOM.createRoot(root).render()
所以,我们可以这样来实现ReactDOM
js
function createRoot(root) {
return {
render() {
}
}
}
const ReactDOM = {
createRoot
}
export default ReactDOM
五、实现createRoot方法
在调用createRoot方法后,我们会创建FilberRootNode对象,这里面我们封装成一个方法,
createContainer方法:
js
function createContainer(root) {
const hostRootFiber = new FilberNode(HostRoot, {}, '')
return new FilberRootNode(root, hostRootFiber)
}
在createRoot方法中调用即可
js
function createRoot(root) {
const filberRootNode = createContainer(root)
return {
render() {
}
}
}