likes
comments
collection
share

JSX 竟然是 Fiber 的爷爷

作者站长头像
站长
· 阅读数 54

我们知道在 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 要求标签必须正确闭合。比如 JSX 竟然是 Fiber 的爷爷 需要写成 <img />, <div><div/>。

  • 为什么标签必须闭合? 个人理解是因为为了方便编译解析。

三、使用驼峰式命名给大部分属性命名

  • 为什么 JSX 最终被转换为 JSX 的对象,JSX 中的属性也会变成 JavaScript 对象中的键值对。但是 JavaScript 中对变量的命名有限制,比如变量名不能包含“-” 或者 class 这样的保留字
  • 注意

由于历史原因, aria-*data-* 属性是以带 - 符号的 HTML 格式书写的。

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 中进行尝试转换。传送门

JSX 竟然是 Fiber 的爷爷

React Element(React 元素)

看完了 JSX 我们来看看经过 _jsxs 处理之后返回的什么:

JSX 竟然是 Fiber 的爷爷 这就是 React Element 了———— 一个包含 props,key ,style,type,等属性的 JS 对象。

Fiber

Fiber 是 VDOM 在 React 中的实现。每个 Fiber 节点被称为 FiberNode 。用来保存 React 元素的类型、对应的DOM元素的信息等等。

FiberNode 是怎么生成的。

FiberNode 是根据 React 元素(React Element)实例化来的,一个 React 元素对应一个FiberNode,下面来看看生成 FiberNode 的具体代码:

JSX 竟然是 Fiber 的爷爷

下面是 FiberNode 的具体属性:

JSX 竟然是 Fiber 的爷爷

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
评论
请登录