JSX 竟然是 Fiber 的爷爷
我们知道在 React16 之后,React 底层引入了 Fiber ,那 Fiber 究竟是什么呢? Fiber 和 JSX 有什么联系?下面我们就来看看:
JSX
JSX 是 JavaScript 语法扩展,可以让你在 JavaScript 文件中书写类似 HTML 的标签。虽然还有其它方式可以编写组件,但大部分 React 开发者更喜欢 JSX 的简洁性,并且在大部分代码库中使用它。
为什么是 JSX
网页是构建在 HTML、CSS 和 JavaScript 之上的。多年以来,web 开发者都是将网页内容存放在 HTML 中,样式放在 CSS 中,而逻辑则放在 JavaScript 中 —— 通常是在不同的文件中!页面的内容通过标签语言描述并存放在 HTML 文件中,而逻辑则单独存放在 JavaScript 文件中。但随着 Web 的交互性越来越强,逻辑越来越决定页面中的内容。JavaScript 负责 HTML 的内容!这也是为什么 在 React 中,渲染逻辑和标签共同存在于同一个地方——组件
JSX 规则
一、只能返回一个根元素
如果想要在一个组件中包含多个元素,需要用一个父标签把它们包裹起来。如果不想用额外的标签。可以用<></>来包裹。
- 为什么只能返回一个根元素
JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
二、标签必须闭合
JSX 要求标签必须正确闭合。比如 需要写成 <img />, <div><div/>。
- 为什么标签必须闭合? 个人理解是因为为了方便编译解析。
三、使用驼峰式命名给大部分属性命名
- 为什么 JSX 最终被转换为 JSX 的对象,JSX 中的属性也会变成 JavaScript 对象中的键值对。但是 JavaScript 中对变量的命名有限制,比如变量名不能包含“-” 或者 class 这样的保留字
- 注意
JSX 转换
在浏览器中无法直接使用 JSX,所以大多数 React 开发者需依靠 Babel 或 TypeScript 来将 JSX 代码转换为 JavaScript。
但是现有的 JSX 转换会把 JSX 转换为 React.createElement(...)
调用。但是这种转换方式也会存在一些问题:
- 使用 JSX,则需在
React
的环境下,因为 JSX 将被编译成React.createElement
(使用 JSX 就需要显示引入 React)。 - 有一些 React.createElement 无法做到的性能优化和简化。具体传送门
基于上面的问题,React 和 Bebal 合作带来了一个全新的,重构过的 JSX 转换的版本并同 React17 一起发布。 新的 JSX 转换带来了一些好处:
- 使用全新的转换,你可以单独使用 JSX 而无需引入 React。
- 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小。
- 它将减少你需要学习 React 概念的数量,以备未来之需。
此次升级是完全向后兼容的,用户升级也是可选的并且旧的 JSX 转换也没有取消支持的计划。
JSX 的产物是什么?
以下面这段 JSX 为例
<div className="box border" style={{color: "red", lineHeight: "20px"}}>
<h1 className={"border " + (_bool ? "" : "pink")}>omg</h1>
<h1
className={classNames("border", _bool ? "" : "pink", "orange")}
style={{display: _bool ? "block" : "none"}}>
omg2
</h1>
<h2>ooo</h2>
<FunctionComponent name="函数组件" info={{data: {info}}} />
<ClassComponent name="class组件" />
</div>
旧的 JSX 转换之后的产物
// 需要显示引入React
import React from 'React';
/*#__PURE__*/React.createElement("div", {
className: "box border",
style: {
color: "red",
lineHeight: "20px"
}
}, /*#__PURE__*/React.createElement("h1", {
className: "border " + (_bool ? "" : "pink")
}, "omg"), /*#__PURE__*/React.createElement("h1", {
className: classNames("border", _bool ? "" : "pink", "orange"),
style: {
display: _bool ? "block" : "none"
}
}, "omg2"), /*#__PURE__*/React.createElement("h2", null, "ooo"), /*#__PURE__*/React.createElement(FunctionComponent, {
name: "\u51FD\u6570\u7EC4\u4EF6",
info: {
data: {
info
}
}
}), /*#__PURE__*/React.createElement(ClassComponent, {
name: "class\u7EC4\u4EF6"
}));
React17 之后新的 JSX 转换之后的产物
// 不需要显示引入React
// 下面的引用由编译器使用
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
className: "box border",
style: {
color: "red",
lineHeight: "20px"
},
children: [/*#__PURE__*/_jsx("h1", {
className: "border " + (_bool ? "" : "pink"),
children: "omg"
}), /*#__PURE__*/_jsx("h1", {
className: classNames("border", _bool ? "" : "pink", "orange"),
style: {
display: _bool ? "block" : "none"
},
children: "omg2"
}), /*#__PURE__*/_jsx("h2", {
children: "ooo"
}), /*#__PURE__*/_jsx(FunctionComponent, {
name: "\u51FD\u6570\u7EC4\u4EF6",
info: {
data: {
info
}
}
}), /*#__PURE__*/_jsx(ClassComponent, {
name: "class\u7EC4\u4EF6"
})]
});
大家可以在 Babel 中进行尝试转换。传送门
React Element(React 元素)
看完了 JSX 我们来看看经过 _jsxs 处理之后返回的什么:
这就是 React Element 了———— 一个包含 props,key ,style,type,等属性的 JS 对象。
Fiber
Fiber 是 VDOM 在 React 中的实现。每个 Fiber 节点被称为 FiberNode 。用来保存 React 元素的类型、对应的DOM元素的信息等等。
FiberNode 是怎么生成的。
FiberNode 是根据 React 元素(React Element)实例化来的,一个 React 元素对应一个FiberNode,下面来看看生成 FiberNode 的具体代码:
下面是 FiberNode 的具体属性:
FiberNode 属性的具体解释
下面是常用的一些属性的解释
export type Fiber = {|
tag: WorkTag, // FiberNode类型: 0为函数组件,1为类组件, 5为原生dom
key: null | string,
type: any, // 函数组件 指函数本身, 类组件指class, 原生dom 指 tagName 如“div”
elementType: any, // 大部分情况和type一样
stateNode: any, // 类组件 指 对应的实例,函数组件为null,原生dom指向对应的真实dom节点
return: Fiber | null, // 指向父 FiberNode 节点
child: Fiber | null, // 指向第一个子 FiberNode
sibling: Fiber | null, // 指向右边的兄弟 FiberNode
index: number, // 当前层级 FiberNode 的索引
ref: // 用来保存dom的引用 或者其他数据
| null
| (((handle: mixed) => void) & {_stringRef: ?string, ...})
| RefObject,
pendingProps: any, // 待更新的props
memoizedProps: any, // 当前的props
// A queue of state updates and callbacks.
updateQueue: mixed, // 更新队列
// The state used to create the output
memoizedState: any, // 类组件中存储state,函数组件存储 hooks链表,原生dom为null
mode: TypeOfMode, // React 模式
// Effect
flags: Flags, // 副作用
subtreeFlags: Flags, //子FIberNode的副作用
deletions: Array<Fiber> | null, // 待删除的 FIberNode
lanes: Lanes, // 当前FiberNode的优先级
childLanes: Lanes, // 子FIberNode的优先级
alternate: Fiber | null, // 指向另一个缓冲区的 FIberNode
|};
JSX 、Fiber 以及真实 Dom 的结构对应关系
下面是一段 JSX 代码
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>类组件</div>
}
}
function Index() {
return <p>函数组件 <span>123</span> </p>
}
function App() {
return (
<div className="App">
<Home />
<Index />
</div>
)
}
对应的 Fiber 结构如下(可以参照前面 FiberNode 的解释来看):
- 注意 正常情况下 React 元素都是和 FiberNode 一一对应的,但是也会有特例,当一个元素的子节点只有一个字符串或者数字时,这个字符串不会生成 FiberNode,比如图中的类组件下面的div,div下面的字符串 “类组件” 是作为props的children属性保存的,不会生成单独的子 FiberNode,而函数组件下面的p标签下面不止一个元素,所以“函数组件”会生成一个FiberNode。这也是 React 内部的一个小的优化手段。
总结
1、 JSX 通过编译得到了 _jsxs 函数。 2、执行 _jsxs 函数就得到了 React Element。 3、 Fiber 构造函数根据 React Element 的属性得到了 FiberNode。
综上,JSX 生出了 _jsxs,_jsxs生出了 React Element,React Element 生出了 FiberNode,因此 JSX 是 Fiber 的爷爷。
最后
感谢大家的阅读,有不对的地方欢迎大家指出来!
参考文章
React 官网 React 18.2.0 源码 React 设计原理 -卡颂
转载自:https://juejin.cn/post/7225902885544837180