React v16.0

September 25, 2017 by Andrew Clark

我们非常激动地宣布 React v16.0 发布了!这些变更包含了一些存在已久的特性,包括碎片(fragments)错误边界portals,支持自定义 DOM 属性,提升[服务端渲染]以及减小库的大小

新的渲染返回类型:碎片和字符串 {#new-render-return-types-fragments-and-strings}

现在你可以从组件的渲染方法中返回一个包含元素的数组。类似于其他数组,但你需要为每一个元素添加key以避免警告:

render() {
  // No need to wrap list items in an extra element!
  return [
    // Don't forget the keys :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

未来,我们很可能会给 JSX 增加一种不需要 key 的特殊碎片语法。

我们也添加了对返回字符串的支持:

render() {
  return 'Look ma, no spans!';
}

查看完整的支持的返回类型列表

更好的错误处理 {#better-error-handling}

之前,渲染时的运行错误会使得 React 进入错误状态,生成加密的错误信息并需要页面进行刷新。为处理这一问题, React 16 使用了更具弹性的错误处理策略。默认情况下,若错误在组件内的渲染或生命周期方法被抛出,整个组件从根开始都不会渲染。这阻止了错误信息的显示。然而,其也可能不是理想的用户体验。

你可以使用错误边界,而非每次出现错误时卸载整个应用。错误边界是用以在子树内部捕获错误并在其位置展示回退 UI 的特殊组件。可以认为错误边界类似于 try-catch 语句,但针对 React 组件。

更多细节,查看我们之前的关于 React 16 的错误处理的文章

Portals {#portals}

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。

render() {
  // React does *not* create a new div. It renders the children into `domNode`.
  // `domNode` is any valid DOM node, regardless of its location in the DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode,
  );
}

可以在 关于 portals 的文档 查看完成的例子。

更好的服务端渲染 {#better-server-side-rendering}

React 16 包含了一个完全重写的服务端渲染器。其是真的快。支持,因此你能够更快地将数据发送到客户端。同时由于新的打包策略 即编译不再进行 process.env 检查(不论你相信与否,在 Node 中读取 process.env 是真的慢!),你不再需要打包React以获得良好的服务端渲染性能。

核心团队成员 Sasha Aickin 写了一篇不错的描述 React 16 服务端渲染的提升。根据 Sasha 的综合的基准测试,React 16 的服务端渲染要比 React 15 快大概三倍。“与带有 process.env 编译后 React 15 比较,在 Node 4 下大约有 2.4x 的性能提升,在 Node 6 下大概有 3.x 的性能提升,在新发布的 Node 8.4下有 3.8x 的性能提升。若与未编译的 React 15进行比较,在最新版本的 Node下,React 16 在服务端渲染有一整个量级的增益!”(根据 Sasha 指出,请注意这些数字是基于综合的基准测试得出的并不一定能反映出真实的性能。)

此外,一旦到达客户端,React 16 更擅长于保留服务端的HTML。其不再要求初始渲染完全匹配服务端的渲染结果。相反,其会尝试尽可能重用现有的 DOM 元素。不再进行 checksums 校验!通常来讲,我们不推荐在客户端渲染和服务端不同的内容,但在某些情况是有用的(例如:时间戳)。

查看关于 ReactDOMServer 的文档 了解更多细节。

支持自定义 DOM 属性{#support-for-custom-dom-attributes}

React 现在会将自定义属性传递给 DOM,而不是忽略不认识的 HTML 和 SVG 属性。这使得我们能够不必在维护的 React 特性的白名单,并能够减少文件体积。

减少文件体积 {#reduced-file-size}

除了这些外,React 16 确实要小于 15.6.1!

  • react 从20.7kb(gzip 后:6.9 kb)减至大小为 5.3 kb(gzip 后:2.2 kb)。
  • react-dom 从141 kb(gzip 后:42.9 kb)减至 103.7 kb(gzip 后:32.6 kb)。
  • react + react-dom 从 161.7 kb(gzip 后:49.8 kb)减至 109 kb(gzip 后:34.8 kb)

二者结合相较于之前的版本体积减少了32%(gzip 后30%)。

体积的差异主要因为包的变化。React 现在使用 Rollup 来进行扁平化的打包以处理不同目标格式,而这使得体积和性能都有了提高。扁平化的包格式也意味着 React 对包的体积大小影响大致一直,无论你如何运行你的应用程序,无论是使用 Webpack、Browserify 还是预构建的 UMD 包或其他系统。

MIT 协议 {#mit-licensed}

以防你错过,React 16 使用 MIT 协议。我们也为那些无法立刻升级的人在 MIT 协议下发布了 React 15.6.2。

新核心架构 {#new-core-architecture}

React 16 是第一个从顶层采用全新的核心架构的版本,代号 “Fiber”。你可以在Facebook 的工程博客上了解这一项目。(Spoiler:我们重写了 React!)

Fiber 负责大部分 React 16 里的新特性,例如错误边界和 fragments。而在接下来的几个版本中,你可以期待我们解锁更多 React 的潜在特性。

可能目前我们的工作最为激动的领域是 异步渲染 - 一种周期性地对浏览器执行调度渲染工作的策略。结果如下,通过异步渲染,应用能够更好的响应,因为 React 避免阻塞了主线程。

这一示例为异步渲染可以解决的问题提供了一个先导(译注:即异步渲染可以解决什么样的问题):

提示:留意旋转的黑色方块。

我们认为异步渲染是一次大动作,并代表了 React 的未来。为保证尽可能平稳地迁移到 v16.0,我们目前仍并没有支持异步特性,但我们非常期待能在接下来几个月将他们推出。保持关注!

安装 {#installation}

React v16.0.0 已可通过 npm 进行安装。

通过 Yarn 安装 React 16,执行:

yarn add react@^16.0.0 react-dom@^16.0.0

通过 npm 安装 React 16,执行:

npm install --save react@^16.0.0 react-dom@^16.0.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>

关于安装说明的细节可以查看文档。

升级 {#upgrading}

尽管 React 16 包含了重大的内部变更,就升级而言,你可以认为这和其他主要版本的 React 发布类似。自今年早些时候我们已经用 React 16 服务了 Facebook 和 Messenger.com的用户,同时我们发布几个 beta 版本和 补丁 版本以解决额外的问题。除了一些小的意外,若你的应用运行在 15.6 版本上且没有任何警告,那么其应该也能够在 16 下正常工作。

新的弃用 {#new-deprecations}

保留(Hydrating)服务端渲染的容器现在有了更清晰的 API 定义。若你想重用服务端渲染的 HTML,使用 ReactDOM.hydrate 而不是 ReactDOM.render。若你只是想做客户端渲染则继续使用 ReactDOM.render 即可。

React 附加套件 {#react-addons}

如之前宣称,我们不再为 React Addons 增加支持。我们期望每个附加套件(除react-addons-perf;查看之前)能够在可见的未来中继续工作,但我们不会再发布额外的更新。

关于如何迁移的建议可以参考之前的博客。

react-addons-perf 无法在React 16中继续工作。在之后我们可能会发布一个新版工具。同时,你可以使用你的浏览器性能工具来测量 React 组件

革新 {#breaking-changes}

React 16 包含了一系列小的变革。这些变革仅会影响一些不常用的示例,而我们并不希望他们会干扰大多数应用。

  • React 15 已对使用 unstable_handleError 进行了限制,不再为错误边界提供文档支持。该方法已重命名为 componentDidCatch。你可以使用 codemod来自动地迁移代码到新的 API
  • 若在生命周期方法内部调用 ReactDOM.renderReactDOM.unstable_renderIntoContainer ,则其返回空。为使其继续工作,你可以使用 portalsrefs
  • setState:

    • 通过 null 调用 setState 不再触发更新。这允许你确定在更新函数里你是否想要重新渲染。
    • 在 render 方法里调用 setState 会触发更新。这不同于之前。无论如何,你不应该在 render 里调用 setState。
    • setState 回调函数(第二个参数)现在会在 componentDidMount / componentDidUpdate之后立刻触发,而非等到所有组件都已渲染。
  • 当使用 <B /> 来替换 <A />B.componentWillMount 现在会在 A.componentWillUnmount 之前触发。之前,在某些情况下,A.componentWillUnmount 会立刻触发。
  • 之前,改变组件的引用总会导致在组件渲染之前解除引用。现在当修改 DOM 节点时,我们将引用的调整置后。
  • 重渲到一个并非由 React 修改的容器里的方式并不安全。这在之前某些情况能够工作但 React 不再支持。现在我们在这一情况下会触发一个警告。你应该清理你组件树中使用 ReactDOM.unmountComponentAtNode 的组件。查看这一例子。
  • componentDidUpdate 生命周期方法将不再传递 prevContext 参数。(查看 #8631
  • 浅渲染器不再调用 componentDidUpdate 因为 DOM 引用并不可用。这也和 componentDidMount 保持一致(之前的版本亦不会调用 componentDidMount )。
  • 浅渲染器不再实现 unstable_batchedUpdates

包 {#packaging}

  • 不再有 react/lib/*react-dom/lib/*。即使在 CommonJS 环境下,React 和 ReactDOM也会预编译为单文件(“扁平包(flat bundles)”)。若你之前依赖于文档未标明的 React 内部方法,则他们不再有效,可以在新的 issue 说明你的具体情况,同时我们也会尽可能为你提供迁移建议。
  • 不再有 react-with-addons.js 构建包。所有兼容的附加组件都单独地在 npm 上发布,若你需要,也对应有单文件的浏览器版本。
  • 在 15.x 介绍的弃用已经从核心 package 中移除。React.createClass 可用 create-react-class 来替代,React.PropTypesprop-types 来代替,react-dom-factories 来代替 React.DOMreact-dom/test-utils 来代替 react-addons-test-utils 以及浅渲染器可用 react-test-renderer/shallow。 查看 15.5.015.6.0 的博客文章来了解代码迁移和自动化代码修改(codemods)的建议。
  • 对于单文件的浏览器构建版本名字和路径做了调整以强调开发和生产版本之前的差异。例如:

    • react/dist/react.jsreact/umd/react.development.js
    • react/dist/react.min.jsreact/umd/react.production.min.js
    • react-dom/dist/react-dom.jsreact-dom/umd/react-dom.development.js
    • react-dom/dist/react-dom.min.js → react-dom/umd/react-dom.production.min.js

JavaScript环境要求 {#javascript-environment-requirements}

React 16 依赖于集合类型 MapSet。若你要支持老式的可能未提供原生支持的浏览器和设备(例如 IE < 11),考虑在你的应用库中包含一个全局的 polyfill,例如 core-jsbabel-polyfill

一个使用 core-js 支持老版浏览器的 React 16 polyfill 环境大致如下:

import 'core-js/es6/map';
import 'core-js/es6/set';

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

React 也依赖于 requestAnimationFrame (甚至包括测试环境)。一个在测试环境下的简单 shim 如下:

global.requestAnimationFrame = function(callback) {
  setTimeout(callback, 0);
};

感谢 {#acknowledgments}

一如既往,此次发布没有开源社区贡献将无法完成。感谢每一位提bug,开PR,回答问题,编写文档以及其他有所贡献的人们!

尤其感谢我们的核心贡献者,尤其是在过去几周的预发布周期的卓越贡献:Brandon DailJason QuenseNathan Hunzaker,和 Sasha Aickin