Skip to content

JSX

JSX 是 JavaScript 语法扩展,结构类似 XML,可以让你在 JavaScript 文件中书写类似 HTML 的标签

为什么要使用 JSX

JSX 是 React 的一大特性

使用 JSX 可以让我们在 JavaScript 中直接使用 HTML 标签,而不用通过 document.createElement 等方法来创建元素,这样可以让我们的代码更加简洁,更加直观

TIP

JSX 和 React

JSX 和 React 是相互独立的东西。虽然它们经常一起使用,但你可以单独使用它们中的任意一个

  • JSX 是一种语法扩展
  • React 是一个 JavaScript 的库

JSX 语法的规则

  • 只能返回一个根元素(组件中包含多个元素时需要用一个父标签把它们包裹起来)
    • 父标签可以是 HTML 标签或 <React.Fragment>(即 <></>
  • 标签必须闭合(如果没有子元素可以使用自闭合标签 <img />
  • 标签的属性命名使用驼峰命名法(class 是一个保留字,需要用 className 来代替)
  • 标签的属性值可以是字符串或表达式,表达式需要使用大括号包裹
    • 引号内的值会作为字符串传递给属性 <App name="maomao" />
    • 在大括号内中可以使用任何 JavaScript 表达式,包括函数调用 <App a={1+2} b={fn()} />
    • {/{ ... }/} 不是什么特殊的语法:只是包在 JSX 大括号内的 JavaScript 对象
  • JSX 注释以 {/* ... */} 形式表示

JSX 语法的转换

React 中所有的元素都是通过 React.createElement 方法创建的。使用 JSX 语法的代码最终会被 Babel 编译为 React.createElement 方法,即 JSX 语法的本质就是 React.createElement 方法

jsx

    // JSX 语法
    import React from 'react'
    const element = <h1 className="greeting">Hello, world!</h1>
    
    // 通过 babel 转化为 React.createElement 方法
    import React from 'react'
    const element = React.createElement('h1', { className: 'greeting' }, 'Hello, world!')

这也是为什么在 React 17 之前我们在使用 JSX 语法时必须要引入 React 的原因,如果不引入 React 就会因为找不到 React.createElement 方法导致编译失败

React.createElement 方法

源码地址 createElement | ReactElement.js

js

    export function createElement(type, config, children) {
      // 用于存储遍历时的属性名
      let propName
    
      // 创建一个用于存储属性的空对象 props
      const props = {}
    
      // key、ref、self、source 均为 React 元素的属性
      let key = null
      let ref = null
      let self = null
      let source = null
    
      if (config != null) {
        // 依次对 ref、key、self 和 source 属性赋值
        if (hasValidRef(config)) {
          ref = config.ref
        }
        // 将 key 转换为字符串
        if (hasValidKey(config)) {
          key = '' + config.key
        }
    
        self = config.__self === undefined ? null : config.__self
        source = config.__source === undefined ? null : config.__source
    
        // 将 config 对象中剩余属性添加到新的 props 对象中
        for (propName in config) {
          if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
            props[propName] = config[propName]
          }
        }
      }
    
      // 计算子元素的个数(去除 type 和 config 剩余的参数都是 children)
      const childrenLength = arguments.length - 2
    
      // 根据剩余的参数的长度分别处理 children 并赋值给 props.children
      if (childrenLength === 1) {
        props.children = children
      } else if (childrenLength > 1) {
        const childArray = Array(childrenLength)
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2]
        }
        props.children = childArray
      }
    
      // 处理 defaultProps
      if (type && type.defaultProps) {
        const defaultProps = type.defaultProps
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName]
          }
        }
      }
    
      return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
    }

源码地址 ReactElement | ReactElement.js

js

    const ReactElement = function (type, key, ref, self, source, owner, props) {
      const element = {
        // 标识是一个 React Element 元素
        $$typeof: REACT_ELEMENT_TYPE,
    
        // 元素的内置属性
        type: type,
        key: key,
        ref: ref,
        props: props,
    
        // 记录创建此元素的组件
        _owner: owner,
      }
    
      return element
    }

createElement 方法接收三个参数:

  • type:元素的类型
  • config:元素的属性
  • children:元素的子元素

该方法会将 config 中的 refkey__self__source 等属性提取出来,剩余的属性放到 props 对象中,最后调用 ReactElement 方法并返回一个 React Element 对象

如何判断一个对象是 React Element 对象

React 提供了一个 isValidElement 方法用于判断一个对象是否是 React Element 对象

js

    export function isValidElement(object) {
      return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE
    }

可以看到,主要满足条件 $$typeof === REACT_ELEMENT_TYPE 的非 null 对象就是一个合法的 React Element 对象

全新的 JSX 转换

React 17 提供了全新的 JSX 转换,在使用时不再需要引入 React

jsx

    // 源代码
    function App() {
      return <h1>Hello World</h1>
    }
    
    // 新的 JSX 转换
    import { jsx as _jsx } from 'react/jsx-runtime'
    
    function App() {
      return _jsx('h1', { children: 'Hello world' })
    }

jsx 方法和上面的 createElement 方法一样,都是对参数进行处理,最后调用 ReactElement 方法返回 React Element 对象

转载自 maomao1996