前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 进阶 - JSX

React 进阶 - JSX

作者头像
Cellinlab
发布2023-05-17 20:44:12
7400
发布2023-05-17 20:44:12
举报
文章被收录于专栏:Cellinlab's BlogCellinlab's Blog

# React 里程碑

  • v16.0
    • 为了解决之前大型 React 应用一次更新遍历大量虚拟 DOM 带来的卡顿文件,React 重写了核心模块 Reconciler,启用 Fiber 架构
    • 为了让节点渲染到指定容器内,更好地实现弹窗功能,推出了 createPortal API
    • 为了捕获渲染中的异常,引入 componentDidCatch API,划分了错误边界
  • v16.2
    • 推出 Fragment,解决数组元素问题
  • v16.3
    • 增加 React.createRef() API,可以通过 React.createRef() 取得 Ref 对象
    • 增加 React.forwardRef() API,解决高阶组件 ref 传递问题
    • 推出新版本 context API,迎接 Provider / Consumer 时代
    • 增加 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 生命周期
  • v16.6
    • 增加 React.memo API,用于控制子组件渲染
    • 增加 React.lazy API,实现代码分割
    • 增加 contextType 让类组件更便捷使用 context
    • 增加生命周期 getDerivedStateFromError 代替 componentDidCatch
  • v16.8
    • 全新 React Hooks 支持,使函数组件也能做类组件的一切事情
  • v17
    • 事件绑定由 document 变成 container,移除事件池

# JSX 会变成什么

代码语言:javascript
复制
const toLearn = ['react', 'vue', 'webpack', 'nodejs'];

const TextComponent = () => <div> hello , i am function component </div>;

class Index extends React.Component {
  status = false;
  renderFoot = () => <div> i am foot</div>;
  render() {
    return <div style={{ marginTop: '100px' }} className="container">
      <div>hello,world</div>
      <React.Fragment>
        <div> 👽👽 </div>
      </React.Fragment>
      my name is alien
      {toLearn.map(item => <div key={item}>let us learn {item}</div>)}
      <TextComponent />
      { this.status ? <TextComponent /> : <div>三元运算</div> }
      { this.renderFoot() }
      <button onClick={() => console.log(this.render())}>打印 render 后的内容</button>
    </div>
  };
}

JSX 元素节点会被编译成 React Element 形式:

代码语言:javascript
复制
React.createElement(
  type,
  [props],
  [...children]
)

createElement 的参数:

  • type:元素类型
    • 如果是组件类型,传入对应的类或函数
    • 如果是 DOM 元素类型,传入 divspan 等字符串
  • props:元素属性
    • 在组件类型中为 props
    • 在 DOM 元素类型中为 attributes 标签属性
  • children:元素子节点
代码语言:javascript
复制
<div>
  <TextComponent />
  <div>hello, world</div>
  let us learn React!
</div>

babel 编译后:

代码语言:javascript
复制
/*#__PURE__*/ React.createElement(
  "div",
  null,
  /*#__PURE__*/ React.createElement(TextComponent, null),
  /*#__PURE__*/ React.createElement("div", null, "hello, world"),
  "let us learn React!"
);

# createElement 处理后

jsx 转换规则:

jsx 元素类型

react.createElement 转换后

type 属性

element 元素类型

react element 类型

标签字符串,如 div

fragment 类型

react element 类型

symbol react.fragment 类型

文本类型

字符串

数组类型

返回数组结构,里面的元素被 react.createElement 转换

组件类型

react element 类型

组件类或组件函数本身

三元运算 / 表达式

先执行三元运算,然后按上面规则转换

看三元运算结果的类型

函数执行

先执行函数,然后按上面规则转换

看函数执行结果的类型

# React 底层调和处理后

最后,在调和阶段,上述 React element 对象的每一个子节点都会形成一个对应的 fiber 对象,然后通过 siblingreturnchild 将每一个 fiber 对象联系起来。

React 针对不同 React element 对象会产生不同 tag (种类) 的 fiber 对象:

代码语言:javascript
复制
export const FunctionComponent = 0;       // 函数组件
export const ClassComponent = 1;          // 类组件
export const IndeterminateComponent = 2;  // 初始化的时候不知道是函数组件还是类组件 
export const HostRoot = 3;                // Root Fiber 可以理解为根元素 , 通过 reactDom.render() 产生的根元素
export const HostPortal = 4;              // 对应  ReactDOM.createPortal 产生的 Portal 
export const HostComponent = 5;           // dom 元素 比如 <div>
export const HostText = 6;                // 文本节点
export const Fragment = 7;                // 对应 <React.Fragment> 
export const Mode = 8;                    // 对应 <React.StrictMode>   
export const ContextConsumer = 9;         // 对应 <Context.Consumer>
export const ContextProvider = 10;        // 对应 <Context.Provider>
export const ForwardRef = 11;             // 对应 React.ForwardRef
export const Profiler = 12;               // 对应 <Profiler/ >
export const SuspenseComponent = 13;      // 对应 <Suspense>
export const MemoComponent = 14;          // 对应 React.memo 返回的组件

jsx 最终形成的 fiber 结构图:

fiber 对应关系:

  • child: 一个由父级 fiber 指向子级 fiber 的指针
  • return:一个子级 fiber 指向父级 fiber 的指针
  • sibling:一个 fiber 指向同级 fiber 的指针

注意,JSX 中 map 数组结构的子节点,外层会被加上 fragment,map 返回数组结构作为 fragment 的子节点。

# 可控性 render

对 render 过程进行介入:

代码语言:javascript
复制
class Index extends React.Component {
  status = false;
  renderFoot = () => <div> i am foot</div>;
  controlRender = () => {
    const reactElement = (
      <div style={{ marginTop: '100px' }} className="container">
        <div>hello,world</div>
        <React.Fragment>
          <div> 👽👽 </div>
        </React.Fragment>
        my name is alien
        {toLearn.map(item => <div key={item}>let us learn {item}</div>)}
        <TextComponent />
        { this.status ? <TextComponent /> : <div>三元运算</div> }
        { this.renderFoot() }
        <button onClick={() => console.log(this.render())}>打印 render 后的内容</button>
      </div>
    );
    console.log(reactElement);

    /** 扁平化 children */
    const { children } = reactElement.props;
    const flatChildren = React.Children.toArray(children);
    console.log(flatChildren);

    /** 除去文本节点 */
    const newChildren: any = [];
    React.Children.forEach(flatChildren, (item) => {
      if (React.isValidElement(item)) {
        newChildren.push(item);
      }
    });

    /** 插入新节点 */
    const lastChildren = React.createElement(
      'div',
      {
        className: 'last'
      },
      'say goodbye'
    );
    newChildren.push(lastChildren);

    /** 修改容器节点 */
    const newReactElement = React.cloneElement(
      reactElement,
      {},
      ...newChildren
    );
  }

  render() {
    return this.controlRender();
  }
}

Q: React.createElementReact.cloneElement 的区别?

A: React.createElement 用于创建一个新的 React element 对象,React.cloneElement 用于修改一个已有的 React element 对象,返回一个新的 React element 对象。

# Babel 解析 JSX

# @babel/plugin-syntax-jsx 和 @babel/plugin-transform-react-jsx

  • @babel/plugin-syntax-jsx 插件可以让 Babel 有效解析 JSX 语法
  • @babel/plugin-transform-react-jsx 内部使用 @babel/plugin-syntax-jsx 插件,可以将 React JSX 转换为 JS 能识别的 createElement 格式
# Automatic Runtime

新版本 React 已经不需要引入 createElement ,这种模式来源于 Automatic Runtime

代码语言:javascript
复制
function Index() {
  return <div>
    <h1>hello,world</h1>
    <span>let us learn React</span>
  </div>
}

编译后:

代码语言:javascript
复制
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
function Index() {
  return /*#__PURE__*/ _jsxs("div", {
    children: [
      /*#__PURE__*/ _jsx("h1", {
        children: "hello,world"
      }),
      /*#__PURE__*/ _jsx("span", {
        children: "let us learn React"
      })
    ]
  });
}

需要在 .babelrc 设置 runtime: automatic

代码语言:javascript
复制
{
  "presets": [
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ]
}

# Classic Runtime

在经典模式下,使用 JSX 的文件需要引入 React:

代码语言:javascript
复制
import React from 'react';
function Index() {
  return <div>
    <h1>hello,world</h1>
    <span>let us learn React</span>
  </div>
}

编译后:

代码语言:javascript
复制
import React from "react";
function Index() {
  return /*#__PURE__*/ React.createElement(
    "div",
    null,
    /*#__PURE__*/ React.createElement("h1", null, "hello,world"),
    /*#__PURE__*/ React.createElement("span", null, "let us learn React")
  );
}

# api 层面的实现

模拟 Babel 处理 JSX:

  1. element.jsx 测试代码:
代码语言:javascript
复制
import React from 'react';

function TestComponent() {
  return <p>hello, React</p>
}

function Index() {
  return <div>
    <span>模拟 babel 处理 jsx 流程</span>
    <TestComponent />
  </div>
}

export default Index;

  1. jsx.js 模拟编译效果:
代码语言:javascript
复制
const fs = require('fs');
const babel = require('@babel/core');

const code = fs.readFileSync('./element.jsx', 'utf-8');
const result = babel.transformSync(code, {
  plugins: [
    '@babel/plugin-transform-react-jsx'
  ]
});
fs.writeFileSync('./element.js', result.code);

  1. element.js 编译后:
代码语言:javascript
复制
import React from 'react';
function TestComponent() {
  return /*#__PURE__*/React.createElement("p", null, "hello, React");
}
function Index() {
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", null, "\u6A21\u62DF babel \u5904\u7406 jsx \u6D41\u7A0B"), /*#__PURE__*/React.createElement(TestComponent, null));
}
export default Index;
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022/9/7,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # React 里程碑
  • # JSX 会变成什么
    • # createElement 处理后
      • # React 底层调和处理后
      • # 可控性 render
      • # Babel 解析 JSX
        • # @babel/plugin-syntax-jsx 和 @babel/plugin-transform-react-jsx
          • # Automatic Runtime
          • # Classic Runtime
        • # api 层面的实现
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档