Typescript 中的 JSX Fragment 语法

2021-03-28

TypeScript 2.6 添加了对 JSX fragments 的支持。在 .tsx 文件中,你现在可以使用新的 <>...</> 语法来创建一个 fragment。

JSX Fragments 背后的动机

在 React 中,从一个组件中返回多个元素是常见的模式。举个例子,下面的组件中会渲染多个列表项:

class List extends React.Component {
  render() {
    return (
      <ul>
        <ListItems />
        <li>Item 3</li>
      </ul>
    );
  }
}

然而,在我们的 ListItems 组件中,我们不能简单地返回多个毗邻的 JSX 元素:

class ListItems extends React.Component {
  render() {
    return (
      <li>Item 1</li>
      <li>Item 2</li>
    );
  }
}

毗邻的多个 JSX 元素必须用一个闭合元素包裹,所以我们添加了包裹元素 div

class ListItems extends React.Component {
  render() {
    return (
      <div>
        <li>Item 1</li>
        <li>Item 2</li>
      </div>
    );
  }
}

不幸的是,添加了这个包括元素后就破坏了列表的结构。我们的 ListItems 组件现在会渲染出以下的 DOM 元素:

<ul>
  <div>
    <li>Item 1</li>
    <li>Item 2</li>
  </div>
  <li>Item 3</li>
</ul>

注意这个 <div> 并不应该在这个结构中。我们应该使用 JSX fragment 语法来避免这种情况:

class ListItems extends React.Component {
  render() {
    return (
      <>
        <li>Item 1</li>
        <li>Item 2</li>
      </>
    );
  }
}

fragment 允许我们在不添加任何包裹节点的情况下返回多个 JSX 元素。现在我们的 List 组件渲染出了期望的结构:

<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>

还有一种方式,我们可以显式地使用 React.Fragment 替代上面介绍的 JSX 语法:

class ListItems extends React.Component {
  render() {
    return (
      <React.Fragment>
        <li>Item 1</li>
        <li>Item 2</li>
      </React.Fragment>
    );
  }
}

这两个版本的 ListItems 组件是相等的,会渲染出完全一样的结果(前提是都在 React 框架中使用我们的 JSX)。

在 Typescript 中编译 JSX Fragments

下面是我们用新语法改写后的 ListItems 组件:

class ListItems extends React.Component {
  render() {
    return (
      <>
        <li>Item 1</li>
        <li>Item 2</li>
      </>
    );
  }
}

如果我们使用 --jsx react(以及 --target es2015)来编译 .tsx 文件,会生成下面的 Javascript 代码:

class ListItems extends React.Component {
  render() {
    return React.createElement(
      React.Fragment,
      null,
      React.createElement("li", null, "Item 1"),
      React.createElement("li", null, "Item 2"),
    );
  }
}

编译器会生成 React.createElement() 方法调用来替换简短的 fragment 语法,并且把 React.Fragment 作为第一个参数传入。

如果我们使用 --jsx preserve(以及 --target es2015)来编译 ListItems组件,我们的 JSX 输出不会有改变(忽略空格):

class ListItems extends React.Component {
  render() {
    return (
      <>
        <li>Item 1</li>
        <li>Item 2</li>
      </>
    );
  }
}
郑超的独立博客