React v16.2.0: Improved Support for Fragments

November 27, 2017 by Clement Hoang

React 16.2 现在已经发布了!最大的增加是提高了对子组件的渲染方法返回多个子元素的支持。我们称这一特性称为 fragments

Fragment 看起来像是空的 JSX 标签。他们能够让 let you group a list of children 而无需添加额外的节点到 DOM 树上:

render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

这一令人激动的新特性能够通过 React 和 JSX 添加来实现。

什么是 Fragments? {#what-are-fragments}

对于一个组件来说,普遍模式是返回一个列表的子组件。用 HTML 展示这个例子的如下:

Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.

React 16 之前的版本,React 实现这一功能的唯一方式是通过用一个额外的元素来包装子节点,通常使用一个 divspan

render() {
  return (
    // Extraneous div element :(
    <div>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </div>
  );
}

为了处理这一限制,React 16.0 增加了对组件的 render 方法返回一个包含元素的数组 的支持。你可以将其他们放进数组里,而不用将子元素包装在一个 DOM 元素内:

render() {
 return [
  "Some text.",
  <h2 key="heading-1">A heading</h2>,
  "More text.",
  <h2 key="heading-2">Another heading</h2>,
  "Even more text."
 ];
}

然而,这和普通 JSX 有一些令人混淆的区别:

  • 数组中的子元素必须用逗号分隔,
  • 数组中的子元素必须有一个唯一的 key 以防止 React 的 key 警告
  • 字符串必须包括在引号内。

为了对 fragment 提供更好的一致性开发体验,React 现在提供了第一等的 Fragment组件以可以用来替换数组。

render() {
  return (
    <Fragment>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </Fragment>
  );
}

你可以像使用其他任何元素一样使用 <Fragment />,而不用改变你写 JSX 的方式。没有逗号,没有 key,没有引号。

Fragment 能够通过 React 对象进行使用:

const Fragment = React.Fragment;

<Fragment>
  <ChildA />
  <ChildB />
  <ChildC />
</Fragment>

// This also works
<React.Fragment>
  <ChildA />
  <ChildB />
  <ChildC />
</React.Fragment>

JSX Fragment 语法 {#jsx-fragment-syntax}

在 Facebook, 片段(Fragments)在我们的代码库中是一个通用模式。我们期望也能被其他团队广泛采用。为了保证开发体验尽可能地便利,我们为 JSX 增加了 fragment 的语法支持:

render() {
  return (
    <>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </>
  );
}

React 中,类似于之前一节展示的例子,是 <React.Fragment /> 的语法糖。(使用 JSX 的非 React 框架可能在编译时有些差异。)

JSX 里的 Fragments 语法受到了来自之前技术如 E4X 中的 XMLList() <></> 构造函数的激励。使用一对空标签其意味不用添加了一个实际的元素到 DOM 结构中。

带有 key 属性的 Fragments {#keyed-fragments}

注意 <></> 语法不接受属性,包括 key。

若你需要一个带有 key 属性的 fragment,你可以直接使用 <Fragment />。一个例子是将一个集合映射到一个片段的数组 — 例如,创建一个列表:

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // Without the `key`, React will fire a key warning
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </Fragment>
      )}
    </dl>
  );
}

key 是唯一一个能够传递给 Fragment 的属性。未来,我们可能还会增加额外属性,如事件处理。

在线演示 {#live-demo}

你可以在 CodePen 体验 JSX fragment。

Fragment 语法支持 {#support-for-fragment-syntax}

在 JSX 支持 fragment 语法将取决你用于构建应用的工具。请对 JSX 社区致力于采用新语法保持些耐心。我们也在和大多数流行项目的维护者紧密的合作:

Create React App {#create-react-app}

试验性支持的 fragment 语法将会在接下来几天中加入到 Create React App。稳定版的发布可能需要更长的时间,由于我们需要等到上游项目的采纳。

Babel {#babel}

对于 JSX fragment 已经在 Babel v7.0.0-beta.31 及以上版本中得到支持!若你已经在使用 Babel 7,很容易将其升级到最新版本的 Babel 和 transform 插件(plugin transform):

# for yarn users
yarn upgrade @babel/core @babel/plugin-transform-react-jsx
# for npm users
npm update @babel/core @babel/plugin-transform-react-jsx

或若你正在使用 react preset

# for yarn users
yarn upgrade @babel/core @babel/preset-react
# for npm users
npm update @babel/core @babel/preset-react

注意 Babel 7 目前技术上仍在 beta 阶段,但稳定版本将很快发布

不幸的是,目前仍然还没有对 Babel 6.x 进行支持,同时目前也没有计划反向移植到旧版中。

Babel 以及 Webpack (babel-loader){#babel-with-webpack-babel-loader}

如果你在使用 Babel 和 Webpack,则不需要额外的步骤,因为 babel-loader 将会使用你的同等安装的 Babel 版本。

Babel 及其他框架 {#babel-with-other-frameworks}

如果你在使用 JSX 及非 React 框架如 Inferno 或 Preact,在 babel-plugin-transform-react-jsx 中有一个编译选项 能够配置 Babel 编译器使其能够将 <></> 这一语法糖编译为一个自定义标签。

TypeScript {#typescript}

TypeScript 已经完全支持 fragment 语法!请升级到 2.6.2 版本。(注意这非常重要,即使你已经升级到了 2.6.1 版本,由于这一支持是作为补丁发布在 2.6.2 上的。)

使用如下命令升级到最新版本的 TypeScript:

# for yarn users
yarn upgrade typescript
# for npm users
npm update typescript

Flow {#flow}

Flow 对 JSX fragment 的语法支持从 0.59 版本 开始!简单运行

# for yarn users
yarn upgrade flow-bin
# for npm users
npm update flow-bin

以升级到最新版本的 Flow。

Prettier {#prettier}

Prettier 将在他们即将发布的 1.9 版本中支持 fragment 语法。

ESLint {#eslint}

当其和 babel-eslint 一同使用时,ESLint 3.x 支持 JSX fragment 语法:

# for yarn users
yarn add eslint@3.x babel-eslint@7
# for npm users
npm install eslint@3.x babel-eslint@7

若你已经在使用,则将其升级:

# for yarn users
yarn upgrade eslint@3.x babel-eslint@7
# for npm users
npm update eslint@3.x babel-eslint@7

确保将下面这行代码添加进你的 .eslintrc.js 文件:

"parser": "babel-eslint"

仅此而已!

注意 babel-eslint 并非由 ESLint 官方支持。我们将在未来几周寻求将对 fragment 的支持添加到 ESLint 4.x 上。

编辑器支持 {#editor-support}

其可能会花费些时间来让你的文本编辑器支持 fragment 语法。请对社区采纳这一最新的变更保持些耐心。同时,如果你的编辑器仍未支持 fragment 语法,你可能会看到些错误或异常的高亮语法。通常来讲,这些错误可以被安全地忽略。

TypeScript 编辑器支持 {#typescript-editor-support}

如果你是一位 TypeScript 用户 — 好消息!Visual Studio 2015Visual Studio 2017 以及 Sublime Text via Package Control 已经支持了 JSX fragment。Visual Studio Code 也将很快进行更新,但需要配置其 TypeScript 版本为 2.6.2 及之后

其他工具 {#other-tools}

对于其他工具,请检查对应的文档以查看其是否支持。然而,如果你被你的工具所阻碍,你可以先采用 <Fragment> 组件并在工具能够正确支持之后用 codemod 将其进行替换为缩写语法。

安装 {#installation}

React v16.2.0 已经发布到 npm 仓库上。

通过 Yarn 安装 React 16,运行:

yarn add react@^16.2.0 react-dom@^16.2.0

通过 npm 安装 React 16,运行:

npm install --save react@^16.2.0 react-dom@^16.2.0

我们也通过 CDN 提供了一个 UMD 构建的 React 版本:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

查阅文档了解安装细节说明

变更日志 {#changelog}

React {#react}

  • 为 React 添加名为 Fragment 的导出(export)对象。(@clemmy#10783 提的 PR)
  • React.Children 的工具集中增加试验性的 Call/Return 类型。(@MatteoVH#11422 提的 PR)

React DOM {#react-dom}

  • 修复当使用多列单选按钮,单选按钮无法被选中。(@landvibe#11227 提的 PR)
  • 修复单选按钮在某些情况下无法接受到 onChange 事件。(@jquense#11028 提的 PR)

React Test Renderer {#react-test-renderer}

  • 修复当在 componentWillMount 调用 setState() 时,其回调函数过早调用的问题。(@accordeiro#11507 提的 PR)

React Reconciler {#react-reconciler}

  • 发布 react-reconciler/reflection 帮助公用的自定义渲染器。(@rivenhk#11683 提及的 PR)

内部变更 {#internal-changes}

感谢 {#acknowledgments}

本次发布由我们的开源贡献者完成。非常感谢提交问题的人,对语法讨论的贡献和审阅 PR,在第三方库中增加 JSX fragment 支持等等!

尤为感谢 TypeScriptFlow 团队,以及 Babel 的维护者们,帮助为新语法在工具上提供了无缝支持。

感谢 Gajus Kuizinas 和其他贡献者在开源社区提出了 Fragment 的原型。