在阅读源码之前,先了解几个问题。
为什么可以用 JSX 来写 react 组件
浏览器又不支持 JSX 语法,为什么可以用 JSX 来写 react 组件?
其实JSX只是给开发者提供便利的一种 JavaScript 语法糖,在 react 项目经过编译阶段时,JSX 会被 babel 编译成对 react 内置方法的调用。可以理解为 babel 帮助我们将 JSX 编译转换为 JavaScript 代码。
如下面的代码:
function App() {
return <div>
hello, react
<Child11 />
<Child12 />
</div>
}
function Child1() {
return <span>child1</span>
}
function Child2() {
return <span>child2</span>
}
编译后的代码为 (也就是我们打包构建后上生产环境的代码) :
function App() {
return React.createElement("div", null, "hello, react", React.createElement(Child11, null), React.createElement(Child12, null));
}
function Child1() {
return React.createElement("span", null, "child1");
}
function Child2() {
return React.createElement("span", null, "child2");
}
所以当浏览器在执行 App 函数组件时,其实调用的就是 React.createElement 方法。所以在 react17 版本之前,我们在写 JSX 文件的时候,必须要引入React,不然编译会报错
在看一下 react17 版本之后的新版的 JSX 转换结果:
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return _jsxs("div", {
children: ["hello, react", _jsx(Child11, {}), _jsx(Child12, {})]
});
}
function Child1() {
return _jsx("span", {
children: "child1"
});
}
function Child2() {
return _jsx("span", {
children: "child2"
});
}
babel 在编译后,已经帮我们引入了 JSX 方法,所以17版本之后,不需要我们手动引用 react。
babel在线编译地址:Babel · The compiler for next generation JavaScript
ReactElement对象
上面说了编译后的代码是执行的 React.createElement 或者 JSX 方法,而这两个方法都是为了创建 ReactElement 对象。
ReactElemenet 对象是 react 用来描述 UI 的对象
主要有这些属性:
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
- $$typeof 用于标识为 ReactElement 对象,
- type 表示元素的类型,可以是div、span等 html 标签,也可以是 React 组件名
- props 作为元素、或组件的属性,如 classname、或者传递给组件的属性,还包含了当前元素的 children 子元素
- key 作为当前元素的唯一标识
- ref 作为传递元素或组件的 ref
ReactElement 对象即描述了当前元素或组件的 UI 属性和内容,也描述了 UI 组件的结构,可以通过 props.children 获取当前 UI 的子 UI 结构,所以 react 可以根据 JSX 代码,生成一棵描述整个应用的虚拟 DOM 树。
当然,光看 ReactElement 对象的这几个属性,只能得知整个应用的结构和内容,而我们还需要知道每个组件节点的状态、数据、副作用等,React 又引人了 Fiber 架构,用内部实于现管理组件状态、渲染、协调更新的数据结构
Fiber
Fiber 是什么
ReactElement 是通过 JSX 创建的描述 UI 的对象,而 Fiber 节点是 ReactElement 在 Fiber 架构中的具体实现,它保存了 UI 的结构,组件的属性(props)组件内部的状态(state)、副作用等等。ReactElement 主要用于描述 UI 结构,是不可变的,而 Fiber 节点则是用于内部实现管理组件状态、渲染、协调更新的数据结构,Fiber 节点会不断地更新,以反映组件的最新状态、props 和其他信息。
每个 Fiber 节点都是一个 JavaScript 对象,包含了与该元素相关的所有信息。关键属性包括:
- type:该 Fiber 节点表示的组件类型(例如函数组件 FunctionComponent / MemoComponent、类组件是 ClassComponent、原生DOM元素是 HostComponent等)。
- key:用于唯一标识子节点数组中的每一个子节点。
- stateNode:对原生DOM元素(即 HostComponent 类型的 fiber ,它是保存着当前 fiber 对应的真实 DOM 阶段;对类组件,它是指向的是 class 组件实例,函数组件类型的 fiber 则保存的是 null。
- child:指向第一个子Fiber节点。
- sibling:指向下一个兄弟Fiber节点。
- return:指向父级Fiber节点。
- pendingProps:在本次更新中传入的新的props。
- memoizedProps:上次渲染完成后的props。
- updateQueue:保存该Fiber节点的状态更新和回调。
- flags:标记该 Fiber 节点在本次更新中所需执行的操作类型,如新增一个元素 则标记 Placement ,更新则标记 Update
fiber 是由 ReactElement 初始化来的,即从 ReactElement 树 转换到 fiber 树
为什么要引人 Fiber 架构
Fiber 架构是 React16 之后才引人的,为的是实现对组件渲染、更新等更精确的管理的数据结构。什么叫更精确的管理呢?
比如将更新、渲染划分为更小的工作单元,每个工作单元对应一个 Fiber 节点,这样就可以减少单个渲染、更新任务的时间
比如支持可中断和恢复的更新。因为 js 是单线程的,如果 React 应用中组件数量庞大,或者某些组件处理数据时间过长,都会导致 js 主线程长期被 React 占用,才会去执行浏览器的其他任务,这就会导致一些用户交互、动画等任务无法立即执行,造成页面卡顿,影响用户体验。而如果支持可中断和恢复的更新,就可以把 React 任务和其他任务分小批量的处理,提高用户体验
比如支持优先级的调度。当同一时间内触发多次更新时,根据更新的优先级进行管理,让用户更关心的高优先级的更新任务先进行更新,提高用户体验。
并发更新。当然JavaScript 是单线程的,无法实现真正的并发,这里的并发是指在同一时间执行多个任务的能力。在 JavaScript 中,由于其单线程特性,我们无法实现真正的并发。然而,通过将任务分解为更小的部分并在事件循环中交替执行它们,我们可以模拟并发。这有助于避免长时间运行的任务阻塞用户界面。
Fiber是怎么在初始化和更新流程中起作用的
双缓存机制
先来了解下什么是双缓存技术。
单缓存模式:假设我们在看一场舞台表演,当一个节目结束表演时,我们需要重新布置第二节目的舞台、灯光、设备等,这个等待对观众是不友好的。
双缓存模式:而假如我们的舞台是多面的旋转舞台,在主舞台节目表演的同时已经把后续节目的舞台布置好,在结束表演的时候,只需要将其他节目的舞台旋转过来作为 主舞台,这时候就能节省搭景的时间,能极大提升观众体验。
这也是双缓存机制的工作原理。
双缓存模式是一种常见的优化技术,通常用于图形渲染和动画中,以确保平滑的界面更新。我们平常玩的许多游戏也是使用的这种渲染机制,游戏引擎会在后台不断地渲染下一帧画面,当下一帧准备好后更新游戏界面,从而实现流畅的游戏画面更新。
React 就是采用双缓存技术来优化性能,在 React 中这种模式的核心思想是,页面 UI 上展示的真实 DOM 树对应的叫 current fiber 树(也就是主舞台),在进行界面更新时,会根据 current fiber 树 以及需要更新的信息,在内存中构建一个新的 fiber 树,叫做 workInProgress fiber tree , 然后根据新的 workInProgress fiber 树与 current fiber 树做对比,更新页面 DOM 元素,最后将 workInProgress fiber 作为页面对应的新的 current fiber 树。
初始化流程
初始化时,因为没有 DOM,所以此时的 current fiber 树是空的,React 会根据 JSX 的树结构在内存中生成 workInProgress fiber 树,然后根据workInProgress fiber 树生成 DOM 树,再将 DOM 树插入到页面中,然后将 workInProgress fiber tree 作 current fiber 树。
更新流程
更新阶段时,会根据 current fiber 树,生成一棵新的 workInProgress fiber 树,因为 fiber 保存着当前节点最新的 状态、副作用、DOM 节点,所以可以根据这 workInProgress fiber 树 和 current fiber 树做对比,将需要更新的 fiber 打上标记,然后根据标记做对应的 DOM 的更新操作,更新完后,这时候将新生成的 workInProgress fiber 再作为 current fiber 树。
总结
一些变量的概念:
current fiber tree :与页面上真实 DOM 树 对应的 fiber 树
workInProgress fiber tree :在内存中生成的 fiber 树
hostRootFiber:current fiber tree 与 workInProgress fiber tree 都有其根 fiber 节点,叫做 hostRootFiber
fiberRoot:整个 React 应用的根节点 fiber,它的 current 所指向的 fiber tree 就是 current fiber tree,可以简单理解为 hostRootFiber 的 父节点
JSX是怎么最终渲染成真实的DOM的
JSX 会被编译成执行React.createElement方法,所以最终代码执行时就是执行React.createElement方法,createElement会创建组件或元素对应的ReactElement对象,最终根据代码的UI结构生成一颗对应的虚拟DOM树,同时也会生成对应的fIber树,fiber树即保存了DOM的属性,结构,组件的内部状态、副作用等等,最终根据fiber树生成真实的DOM树,渲染到页面上。
ReactEelement对象和Fiber对象的区别和联系
每个 ReactElement 节点都有其一一对应的Fiber节点,Fiber 是根据ReactElement创建的(除了根节点的Fiber)
ReactElement 节点主要是用来存储着UI结构,Fiber 节点既存储了UI的结构,还存储了有关组件和元素的信息,以及任务的状态和优先级等信息
ReactElement 主要作用是 在初始化时,根据ReactElement保存的UI结构,生成对应的Fiber树。而 Fiber 则是在初始化和更新过程中,用于协调更新和管理任务的执行,Fiber 架构使得 React 可以实现更精细的控制和任务调度。
总结起来,ReactElement 对象用于描述 DOM 的层次结构,它轻量且不包含组件状态和副作用。Fiber 对象用于实现渲染和协调更新,包括组件的状态、更新队列、副作用等。这种分离使得 React 能够更高效地处理渲染和协调,并支持更复杂的特性。两者相辅相成,共同构建了 React 的核心功能。

717

被折叠的 条评论
为什么被折叠?



