JSX的转换

179 阅读2分钟

React的JSX语法是不能直接运行的宿主环境的,需要将 JSX 代码转换为 js 代码才能在宿主环境运行。

转换后的代码就是在源码中实现的 React.createElement 方法,jsx 方法等价于 React.createElement 方法。至于转换过程,交给 babel 去处理

React17之前的转换

import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}

转换后

import React from 'react';

function App() {
    return React.createElement('h1', null, 'Hello world');
}

React17之后的转换

React17 与 Babel 的合作,使新的 JSX 转换可以让我们不引入 React 既可使用 JSX 了。 因为在编译时,就自动从 react/jsx-runtime 中 引入了 jsx 方法,import {jsx as _jsx} from 'react/jsx-runtime'。相比之前的全量引入,可以一定程度上减少包的提及大小

具体可以查看官方博客 介绍全新的 JSX 转换

输入

import React from 'react';

function App() {
    return <h1>Hello World</h1>;
}

输出

// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
    return _jsx('h1', { children: 'Hello world' });
}

可在 babel 官网 babeljs.io/repl# 尝试

React.createElement 方法的实现

要创建一个 React 元素,就需要调用 React.createElement 方法,回顾一下我们平时开发中,React 元素包含哪些属性

  1. 构造一个 ReactElement 元素,返回 ReactElementType

    const ReactElement = (
        type: Type,
        key: Key,
        ref: Ref,
        props: Props
    ): ReactElementType => { 
        return {
            /**
             * 识别是否为 ReactElement
             * 为了防止这个值被滥用,将其值定义为全局唯一的 Symbol 类型的值
             * */
            "@@typeof": REACT_ELEMENT_TYPE,
            type,
            key,
            ref,
            props
        };
    };
    
  2. React 上的 createElement 方法就对应的是 jsx/jsxDEV 方法,不过要区分环境。开发环境下,createElement 对应的是 jsxDEV,生产环境对应的的 jsx

// 打包后:react/jsx-dev-runtime
// export const jsxDEV = ...

// 打包后:react/jsx-runtime
export const jsx = (
    type: ElementType,
    config: any,
    ...maybeChildren: any[]
) => {
    let key: Key = null;
    let ref: Ref = null;
    const props: Props = {};

    // 处理 props 上除了 children 的属性
    for (const prop in config) {
       const val = config[prop];
       // key 和 ref 不放到 props 上
       if (prop === "key") {
          if (val !== undefined) {
             key = "" + val;
          }
          continue;
       }
       if (prop === "ref") {
          if (val !== undefined) {
             ref = val;
          }
          continue;
       }
       // 如果是它自己的prop,就给它赋值。避免赋值给原型上的属性
       if ({}.hasOwnProperty.call(config, prop)) {
          props[prop] = val;
       }
    }

    // 处理children
    const maybeChildrenLength = maybeChildren.length;
    if (maybeChildrenLength) {
       // 如果只有一个children,则将数组解开,直接将children赋值给props.children
       if (maybeChildrenLength === 1) {
          props.chidren = maybeChildren[0];
       } else {
          props.chidren = maybeChildren;
       }
    }

    return ReactElement(type, key, ref, props);
};

jsxDEV 函数会生成带有调试信息的 React 元素,用于在开发模式下生成 JSX 元素的辅助函数。

而 jsx 方法是 Babel 或其他 JSX 转换工具生成的函数,用于将 JSX 代码转换为普通的 JavaScript 代码, 用于编译后的环境