React v16.0(译)

原文:https://facebook.github.io/react/blog/2017/09/26/react-v16.0.html

我们很兴奋地向大家宣布 React v16.0 release 版本的正式发布。此版本包含了一些长期的功能改进,包括 fragmentserror boundariesportals,支持 自定义 DOM 属性,改进的 服务端渲染 以及 减小了文件体积

render 新的返回类型:fragments 和 strings

在 React v16 中,你可以在组件的 render 方法里返回一个元素的数组。和之前的列表渲染一样,你需要去为每个元素添加一个 key,来阻止 key 的警告:

render() {
  // 不再需要将列表项放在一个额外的元素中
  return [
    // 不要忘记 key :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

在将来,我们有望添加一个特别的片段语法到 JSX 中,使得不在需要显式地指定 keys。

与此同时,我们还添加了返回 strings 的支持:

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

查看所有支持的返回类型

更好的错误处理

在之前的版本中,渲染过程中的运行时错误可能会导致 React 陷入错误的状态,输出一些奇怪的错误信息,并且要求刷新页面来恢复。针对这一问题,React 使用了一个适应性更强的错误处理策略。在此之前,如果一个错误在组件渲染或生命周期方法中被抛出,React 会默认将整个组件树从根组件上解除绑定。这虽然阻止了错误数据的展示,但是可能会影响用户的体验。

新版本中,你可以使用 error boundaries 来代替每次出错时解绑整个应用的做法。Error boundaries 是一个特别的组件,它可以捕获子树中的异常并展示一个回退的视图。我们可以把 error boundaries 理解为类似 try-catch 的东西,只不过它是针对 React 组件而设计的。

想要了解更详细的内容,可以阅读 Error Handling in React 16

Portals

Portals 提供了一个顶级的方法,使得我们有能力把一个子组件渲染到父组件 DOM 层级以外的 DOM 节点上。

render() {
  // React 不会新建一个 div。它将在指定的 `domNode` 上进行渲染。
  // `domNode` 是一个合法的 DOM 节点,它可以在 DOM 树中的任意位置。
  return ReactDOM.createPortal(
    this.props.children,
    domNode,
  );
}

完整的例子,见 portals 文档

更好的服务端渲染

React 16 彻底重写了服务端渲染。新的服务端渲染速度非常快。它支持流的方式进行传输,这意味着你可以更快地发送字节到客户端。另外要感谢 新的打包策略,它在编译时移除了 process.env 的检查(信不信由你,在 Node 中读取 process.env 真的很慢!),你不再需要打包 React 来获得好的服务端渲染性能。

核心团队成员 Sasha Aickin 写了一篇 great article describing React 16's SSR improvements。按照 Sasha 模拟的性能分析,React 16 的服务端渲染速度大约比 React 15 快三倍。“与移除了 process.env 的 React 15 比较,在 Node 4 下大约提升了 2.4 倍,Node 6 下大约提升了 3 倍,新的 Node 8.4 release 下大约提升了 3.8 倍”。如果和没有进行编译的 React 15 比较,React 16 在最新的 Node 上的服务端渲染速度有着巨大的优势。(正如 Sasha 指出的,这些数据是基于模拟的性能分析得到的,因此并不完全反映了真实情况下的性能。)

此外,React 16 能够更好地混合服务端渲染完传递到客户端的 HTML。它不再要求初始化渲染完全匹配服务端的结果。相反,它会尽量地去复用已有的 DOM。不再进行校验!通常来说,我们不推荐你在服务端和客户端渲染不同的内容,但是它在一些情况下会很有用。

阅读 ReactDOMServer 文档 获得更多详细信息。

支持自定义 DOM 属性

之前的版本 React 会忽略不认识的 HTML 和 SVG 属性,而 React 16 将会把它们传递给 DOM。这可以让我们去掉大部分 React 的属性白名单,从而减小文件的体积。

减小文件体积

尽管上面提到了这么多新功能,然而 React 16 体积相比于 15.6.1 版本却减少了!

  • react 从 20.7kb(6.9kb gzipped)减小到 5.3 kb(2.2 kb gzipped)。
  • react-dom 从 141kb(42.9kb gzipped)减小到 103.7kb(32.6kb gzipped)。
  • react + react-dom 从 161.7kb(49.8kb gzipped)减小到 109kb(34.8kb gzipped)。

组合起来的体积 相比于之前的版本减少了 32%(30% gzipped)

文件体积的减小一定程度上还与打包方式的改变有关。React 现在使用 Rollup 来构建每个不同目标格式的扁平包,以至于文件体积和运行时性能都有了提升。以扁平化格式打包也意味着无论你在 Webpack、Browserify、预编译的 UMD 甚至任何构建系统中构建你的应用,对 React 打包的影响都基本一致。

译注:webpack3 之前版本的打包方式会用函数将每个模块包裹,一是闭包会降低 JavaScript 的执行效率;而是大量的包裹代码增加了文件体积。而 Rollup 将打包的所有代码都放在同一个地方,然后一次性执行,从而生成更简洁、更简单的代码,从而启动更快。这也是文档中扁平化打包的解释。

MIT 开源许可

如果你错过了这条消息,那么我们在这里宣布 React 16 的开源许可将切换到 MIT。对于不能够立刻升级到 React 16 的用户,我们发布了 MIT 开源许可的 React 15.6.2 版本。

新的核心架构

React 16 是建立在新的核心架构 “Fiber” 上的第一个版本。你可以在 Facebook 工程师博客 了解到更多 “Fiber” 相关的内容。

Fiber 对 React 16 中大部分新的特性都起到了重要作用,例如 error boundaries 和 fragments。在接下来的几个 release 版本中,我们将开始去解锁 React 所有的可能性,你可以期待更多的新特性。

我们所做的最令人兴奋的工作大概是异步渲染,它是一个通过在浏览器中周期性地执行任务达到协同调度渲染工作的策略。异步渲染为我们带来的是应用响应速度更快,因为 React 阻止了主线程的阻塞。

我们认为异步渲染是一个重大的改进,它代表了 React 的未来。为了能够尽可能平滑地迁移到 v16.0 版本,我们没有启用任何异步特性,但是我们非常兴奋在接下来的几个月中开启它们。敬请关注!

安装

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 也提供了 React 的 UMD 构建版本:

<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>

详细的安装说明请参考文档。

升级

虽然 React 16 包含了重大的内部改变,但是就升级而言,你可以认为和其他任何重要的 React release 版本一样。年初的时候,我们已经在 Facebook 和 Messenger.com 中使用 React 16,并且释放了一些 beta 和 release 候选版本来解决额外的问题。除了少数例外,如果你的代码在 15.6 版本中能没有警告运行,那么它也能够在 16 中正常运行

新弃用的内容

混合服务端渲染的容器的 HTML 现在有了一个明确的 API。如果你要恢复服务端渲染的 HTML,使用 ReactDOM.hydrate 代替 ReactDOM.render。如果你只需要客户端渲染,那么继续使用 ReactDOM.render

React Addons

如我们之前宣布的,我们 不再继续维护 React Addons。我们期望每个 addon 最新的版本(除了 react-addons-perf)能够在可预见的将来一直可用,但是我们将不再发布额外的更新。

如何进行迁移 请参考之前的声明。

react-addons-perf 在 React 16 中将不再使用。我将会在将来为其发布新的版本。与此同时,你可以使用浏览器的性能分析工具来分析 React 组件的性能。

破坏性更改

React 16 包含了一些破坏性的更改。这些更改只影响一些不常见的使用场景,因为我们不希望它们破坏大部分应用的正常运行。

  • React 15 没有在文档中写明使用 unstable_handleError 来支持 error boundaries。这个方法现在被重命名为 componentDidCatch。你可以使用 codemod 来自动升级到新的 API。
  • ReactDOM.renderReactDOM.unstable_renderIntoContainer 在生命周期方法中被调用时不再返回 null。如果要这么做,你可以使用 portalsrefs
  • setState:
    • setState 用 null 调用时,将不再触发更新。这允许你在一个更新方法中决定是否需要重新渲染。
    • 在 render 方法中直接调用 setState 总是会引起一次更新。之前版本的行为有所不同。无论如何,你不应该在 render 方法中调用 setState
    • setState 第二个回调参数现在不再在 componentDidMountcomponentDidUpdate 后立刻触发,而是在所有组件重新渲染完成后进行触发。
  • 当使用 <A /> 替换 <B />B.componentWillMount 现在总是在 A.componentWillUnmount 之前执行。在此之前,A.componentWillUnmount 在某些情况下会先执行。
  • 在此之前,改变一个组件的 ref 将总是在组件 render 方法调用前分离 ref。现在,我们先应用改变到 DOM,再改变 ref。
  • 重新渲染组件到一个被 React 以外内容修改的容器是不安全的。此前的某些场景下是有效的,但是从不应该被支持。在这些场景下,我们现在会给出警告。相反地,你应当使用 ReactDOM.unmountComponentAtNode 方法清理你的组件树。见此例子
  • componentDidUpdate 声明周期函数不在接收 prevContext 参数。(见 #8631
  • 前渲染不再调用 componentDidUpdate,因为 DOM refs 不可用。这和componentDidMount 是一致的(componentDidMount 在之前的版本就不会被调用)。
  • 浅渲染不再实现 unstable_batchedUpdates

译注:Shallow Rendering (浅渲染)指的是,将一个组件渲染成虚拟 DOM 对象,但是只渲染第一层,不渲染所有子组件,所以处理速度非常快。它不需要 DOM 环境,因为根本没有加载进 DOM。

打包

  • 新版本没有了 react/lib/*react-dom/lib/*。即使在 CommonJS 环境,React 和 ReactDOM 预编译成单个文件(“扁平化的包”)。 如果你之前依赖了 React 内部未在文档中声明的模块,并且它们不在有效了,请在新的 issue 中让我们知道这种特定的情况,我们会尝试给你升级的策略。
  • 打包时将不再包括 react-with-addons.js。所有兼容的 addons 已经单独发布到了 npm,并且如果有需要的话,我们提供了单独的浏览器版本。
  • 15.x 中的弃用警告我们已经从核心包中移除了。React.createClass 现在已经分离到 create-react-class 模块,React.PropTypes 分离到 prop-types 模块,React.DOM 分离到 react-dom-factories 模块, react-addons-test-utils 分离到 react-dom/test-utils 模块, shallow renderer 分离到 react-test-renderer/shallow 模块。具体的代码升级和自动升级代码见 15.5.015.6.0 两篇博客。
  • 为了强调开发环境和生产环境的区别,构建得到的供浏览器使用的单文件的名字和路径都改变了。示例:
    • react/dist/react.jsreact/umd/react.development.js
    • react/dist/react.min.js → -react/umd/react.production.min.js
    • react-dom/dist/react-dom.jsreact-dom/umd/react-dom.development.js
    • react-dom/dist/react-dom.min.jsreact-dom/umd/react-dom.production.min.js

JavaScript 环境要求

React 16 依赖了集合类型 MapSet。如果你需要支持不原生提供这两个集合类型的旧的浏览器和设备(例如:IE < 11),可以考虑在你打包的应用中包含全局 polyfill,例如 core-jsbabel-polyfill

一个使得 React 16 支持就浏览器的 polyfilled 环境大概是这样的:

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);
};
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。