[译]React 17终于发布RC版本了 官方竟说17是个过渡版!

前言

半个月前Vue 3.0刚刚发布了rc版本,React就紧随其后发布了rc版本。javascript

不过相比于Vue3对Vue2.x能力的巨大提高,React17对React16.x好像并无什么很给力的更新。html

在GitHub上的reactjs/reactjs.org文档中甚至出现了这样一句话:

没有任何新特性!这届React有点皮啊!java

那它到底更新了个啥呢?我们来把这个文档翻译一下看看:react

译文

文档地址: https://github.com/reactjs/reactjs.org/blob/c30ff1e39b9fca747198c028a33300656a90e612/content/blog/2020-08-10-react-v17-rc.md
标题 做者
React 17.0 : 没有新特性 gaearon rachelnabors

今天,咱们发布了React v17的第一个 RC 版本。自上一个主要版本的React至今已经有两年半的时间了,按照咱们的标准,时间跨度有些长了!在此篇博客中,咱们将讲解这次主要版本对你的影响以及如未尝试它。git

无新特性

React 17不太寻常,由于它没有添加任何面向开发人员的新功能,而主要侧重于升级简化React自己github

咱们正在积极开发React的新功能,但它们并不属于此版本。React 17是咱们进行深度推广战略的关键所在。web

此版本之因此特殊,你能够认为React 17是一个过渡版,它会使得由一个React版本管理树嵌入到另外一个React版本管理树中时会更加安全。shell

逐步升级

在过去七年的时间里,React一直遵循着all-or-nothing的升级策略。你能够继续使用旧版本,也能够将整个应用程序升级至新版本。但没有介于二者之间的状况。npm

此方式一直延续至今,但咱们确遭遇了all-or-nothing升级策略的局限性。许多API的变动,例如,反对使用 legacy context API时,并不能以自动化的方式来完成。至今可能大多数应用程序从未使用过它们,但咱们仍然选择在React中支持它们。咱们必须在无限期支持过期的API或针对某些应用仍使用旧版本 React 间进行选择。但这两个方案都不合适。react-native

所以,咱们想提供另外一种方案。

React 17开始支持逐步升级React版本。当从React 15升到16时(或者从 React 16升到17时),一般会一次升级整个应用程序。这适用于大部分应用程序。可是,若是代码库是在几年前编写的,而且并无获得很好的维护,那么升级它会变得愈来愈有挑战性。尽管能够在页面上使用两个版本的React,可是直到React 17依然会出现events问题。

咱们使用React 17解决了许多诸如此类的问题。这将意味着当React 18或将来版本问世时,你将有更多选择。首选仍是像之前同样,一次升级整个应用程序。但你也能够选择逐步升级你的应用程序。例如,你可能会将大部分应用程序迁移至React 18,但在React 17上保留一些延迟加载的对话框或子路由。

但这不意味着你必须逐步升级。对于大部分应用程序来讲,一次性所有升级还是最好的解决方案。加载两个React版本,即便其中一个是按需延迟加载的,仍然不太理想。可是,对于没有积极维护的大型应用来讲,能够考虑此种方案,而且 React 17开始能够保证这些应用程序不落伍。

为了实现逐步升级,咱们须要对React的事件系统进行一些更改。而这些更改可能会对代码产生影响,这也是React 17成为主要版本的缘由。实际上,十万个以上的组件中受影响的组件不超过20个,所以咱们但愿大多数应用程序均可以升级到React 17,而不会产生太多影响。若是遇到问题的话能够联系咱们

逐步升级的示例

咱们准备了一个示例(GitHub)仓库,展现了如何在必要时延迟加载旧版本的React。该示例使用了Create React App进行构建,但对其余工具采用相似的方法应该也适用。咱们欢迎使用其余工具的开发者编写demo并提交pr。

注意: 咱们已将 其余的更新推迟到React 17以后。此版本的目标是实现逐步升级。若是升级React 17太困难的话,咱们的目标会没法实现。

更改事件委托

从技术上讲,始终能够在应用程序中嵌套不一样版本的React。但因为React事件系统的工做原理致使很难实现。

在React组件中,一般会内联编写事件处理:

<button onClick={handleClick}>

与此代码等效的DOM操做以下:

myButton.addEventListener('click', handleClick);

但对大多数事件来讲,React并不会将它们附加到DOM节点上。相反,React会直接在document节点上为每种事件类型附加一个处理器,这被称为事件委托。除了在大型应用程序上具备性能优点外,它还使添加相似于replaying events这样的新特性变得更容易。

自从其发布以来,React就一直自动进行事件委托。当document上触发DOM事件时,React会找出调用的组件,而后 React事件会在组件中向上"冒泡"。但实际上,原生事件已经冒泡出了"document"级别,React是在document中安装的事件处理器。

但这就是逐步升级的困难所在。

若是页面上有多个React版本,他们都将在顶层注册事件处理器。这会破坏e.stopPropagation() 若是嵌套树结构中阻止了事件冒泡,但外部树依然能接收到它。这会使不一样版本React的嵌套变得十分困难。这种担心并非没有根据的 —— 例如,四年前Atom编辑器就遇到了相同的问题

这也是咱们为何要改变React底层附加事件方式的缘由。

在React 17中,React将再也不向document添加事件处理器。而会将事件处理器附加到渲染React树的根DOM节点中:

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);

在React 16或更早版本中,React会对大多数事件执行document.addEventListener()。React 17将会在底层调用rootNode.addEventListener()。

多亏了这个更改,如今能够更加安全地进行新旧版本React树的嵌套。请注意,要使其正常工做,两个版本都必须为17或更高版本,这就是为何强烈建议升级到React 17的根本缘由。从某种意义上讲,React 17是一个过渡版本,使逐步升级成为可能。

此更改还让React嵌入使用其余技术构建的应用程序变得更加容易。例如,若是应用程序的"外壳"是用jQuery编写的,但其中较新的代码是用React编写的,则React代码中的e.stopPropagation()会阻止它影响jQuery的代码 —— 就像你所期盼的那样。换句话说,若是你再也不喜欢React并想重写应用程序(好比用jQuery),则能够从外层开始将 React转换为jQuery,而不会破坏事件冒泡。

经核实,多年来在issue tracker上报告的许多问题都已被新特性解决,这些问题大多都与将React与非React代码集成有关。

注意: 你可能想知道这是否会破坏根容器以外的 Portals。答案是React还会监听portals容器上的事件,因此这不是问题。

解决隐患

与其余重大更新同样,可能须要对代码进行调整。在Facebook,咱们在成千上万个模块中大约调整了十个模块以适应此次更新。

例如,若是模块中使用document.addEventListener(...)手动添加了DOM监听,你可能但愿能捕获到全部React 事件。在React 16或更早版本中,即便你在React事件处理器中调用e.stopPropagation(),你建立的DOM监听仍会触发,这是由于原生事件已经处于document级别。使用React 17冒泡将被阻止(按需),所以你的document级别的事件监听不会触发:

document.addEventListener('click', function() {
  // 若是React组件调用了e.stopPropagation()
  // 那么这个自定义监听函数不会收到click事件
});

你能够将监听转换为使用事件捕获来修复此类代码。为此,你能够将{ capture: true }做为 document.addEventListener的第三个参数传递:

document.addEventListener('click', function() {
  // 如今这个事件处理函数使用了事件捕获,
  // 因此它能够接收到全部的点击事件!
}, { capture: true });

请注意,此策略在全局上具备更好的适应性。例如,它可能会修复代码中现有的错误,这些错误在 React 事件处理器外部调用 e.stopPropagation() 发生。换句话说,React 17的事件冒泡更接近常规DOM

其余重大更改

咱们将 React 17中的重大更改保持在最低水平。例如,它不会删除之前版本中弃用的任务方法。可是,它的确包含一些其余重大更改,根据经验,这些更改会相对安全。整体而言,因为这些因素的存在,在十万个以上的组件中受影响的组件不超过20个。

对标浏览器

咱们对事件系统进行了一些较小的更新:

  • onScroll事件再也不冒泡,以防止出现一些混淆
  • React的onFocus和onBlur事件已在底层切换为原生的focusin和focusout事件。它们更接近React现有行为,有时还会提供额外的信息。
  • 捕获事件(例如,onClickCapture)如今使用的是实际浏览器中的捕获监听器。

这些更改会使React与浏览器行为更接近,并提升了互操做性。

注意: 尽管从React 17把focus事件切换成了focusin,但onFocus并未影响冒泡行为。在React中,onFocus事件老是冒泡的,它在React 17中继续冒泡,由于一般它是一个更有用的默认值。查看 这个sandbox,了解能够针对不一样的特定用例添加的不一样检查。

去除事件池

React 17中移除了"event pooling(事件池)"。它并不会提升现代浏览器的性能,甚至还会使经验丰富的开发者一头雾水:

function handleChange(e) {
  setData(data => ({
    ...data,
    // This crashes in React 16 and earlier:
    text: e.target.value
  }));
}

这是由于React在旧浏览器中重用了不一样事件的事件对象,以提升性能,并将全部事件字段在它们以前设置为null。在 React 16及更早版本中,使用者必须调用e.persist()才能正确的使用该事件,或者正确读取须要的属性。

在 React 17 中,此代码能够按照预期效果执行。旧的事件池优化操做已被完成删除,所以,使用者能够在须要时读取事件字段。

这改变了行为,所以咱们将其标记为重大更新,但在实践中咱们没有看到它在Facebook上形成影响(甚至还修复了一些bug!)。请注意,e.persist()在 React事件对象中仍然可用,只不过没有任何效果罢了。

反作用清理时机

咱们正在使useEffect和清理函数的时机保持一致。

useEffect(() => {
  // This is the effect itself.
  return () => {
    // This is its cleanup.
  };
});

大多数反作用(effect)不须要延迟刷新视图,所以React在屏幕上反映出更新后当即异步执行它们(在极少数状况下,你须要一种反作用来阻止重绘。例如,若是须要获取尺寸和位置,请使用useLayoutEffect)。

然而,反作用清理函数(若是存在)在React16中同步运行。咱们发现,对于大型应用程序来讲,这不是理想选择,由于同步会减缓视图的更新(例如,切换标签)。

在React 17中,反作用清理函数会异步执行 —— 若是要卸载组件,则清理会在视图更新后运行。

这反映了反作用自己如何更紧密地运行。在极少数状况下,你可能但愿依靠同步执行,能够改用useLayoutEffect来代替。

注意: 你可能想知道这是否意味着你如今将没法修复有关未挂载组件上的setState的警告。没必要担忧,React专门处理了这种状况,而且不会在卸载和清理之间短暂间隔内发出setState的警告。 所以,取消代码的请求或间隔几乎老是能够保存不变的。

此外,React 17会根据它们在tree中的位置,以与效果相同的顺序执行cleanup。在之前的时候顺序有时会不一样。

潜在隐患

可复用的库可能须要对此状况进行深度测试,但咱们只遇到了几个组件会由于此问题中断执行。好比:

useEffect(() => {
  someRef.current.someSetupMethod();
  return () => {
    someRef.current.someCleanupMethod();
  };
});

问题在于someRef.current是可变的,所以在运行清除函数时,它可能已经设置为null。解决方案是在反作用内部存储会发生变化的值:

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

咱们不但愿此问题对你们形成影响,咱们提供的eslint-plugin-react-hooks/exhaustive-deps的lint插件(请确保在项目中使用它)会对此状况发出警告。

返回一致的undefined错误

在React 16及更早版本中,返回undefined始终会报错:

function Button() {
  return; // Error: Nothing was returned from render
}

这很容易无心间返回undefined:

function Button() {
  // 这里忘记了写ruturn,因此这个组件返回了一个undefined。
  // React会报错而不会忽略它。
  <button />;
}

之前,React只对class和函数组件执行此操做,但并不会检查forwardRef和memo组件的返回值。这是因为编码错误致使。

在React 17中,forwardRef和memo组件的行为会与常规函数组件和class组件保持一致。在返回undefined时会报错

let Button = forwardRef(() => {
  // 这里忘记了写ruturn,因此这个组件返回了一个undefined。
  // React17会报错而不会忽略它。
  <button />;
});

let Button = memo(() => {
  // 这里忘记了写ruturn,因此这个组件返回了一个undefined。
  // React17会报错而不会忽略它。
  <button />;
});

对于不想进行任何渲染的状况,请return null。

原生组件栈

当你在浏览器中遇到错误时,浏览器会为你提供带有JavaScript函数的名称及位置的堆栈信息。然而JavaScript堆栈一般不足以诊断问题,由于React树的层次结构可能一样重要。你不只要知道哪一个Button抛出了错误,并且还想知道 Button在React树中的哪一个位置。

为了解决这个问题,当你遇到错误时,从React 16开始会打印"组件栈"信息。尽管如此,它们仍然不如原生的JavaScript堆栈。特别是它们在控制台中不可点击,由于React不知道函数在源代码中的声明位置。此外,它们在生产中几乎无用。不一样于常规压缩后的JavaScript堆栈,它们能够经过sourcemap的形式自动恢复到原始函数的位置,而使用React组件栈,在生产环境下必须在堆栈信息和bundle大小间进行选择。

在React 17中,使用了不一样的机制生成组件堆栈,该机制会将它们与常规的原生JavaScript堆栈缝合在一块儿。这使得你能够在生产环境中得到彻底符号化的React组件堆栈信息。

React实现这一点的方式有点很是规。目前,浏览器没法提供获取函数堆栈框架(源文件和位置)的方法。所以,当 React捕获到错误时,将经过组件上述组件内部抛出的临时错误(并捕获)来重建其组件堆栈信息。这会增长崩溃时的性能损失,但每一个组件类型只会发生一次。

若是你对此感兴趣,能够在这个PR中阅读更多详细信息,可是在大多数状况下,这种机制不会影响你的代码。从使用者的角度来看,新功能就是能够单击组件堆栈(由于它们依赖于本机浏览器堆栈框架),而且能够像常规JavaScript错误那样在生产中进行解码。

构成重大变化的部分是,要使此功能正常工做,React将在捕获错误后在堆栈中从新执行上面某些函数和某些class构造函数的部分。因为渲染函数和class构造函数不该具备反作用(这对于SSR也很重要),所以这不会形成任何实际问题。

移除私有导出

最后,值得注意的重大变化是咱们删除了一些之前暴露给其余项目的React内部组件。特别是,React Native for Web过去经常依赖于事件系统的某些内部组件,但这种依赖关系很脆弱且常常被破坏。

在React 17中,这些私有导出已被移除。据咱们所知,React Native for Web是惟一使用它们的项目,它们已经完成了向不依赖那些私有导出函数的其余方法迁移。

这意味着旧版本的React Native for Web不会与React 17兼容,可是新版本可使用它。实际上,并无太大的变化,由于React Native for Web必须发布新版本以适应其内部React的变化。

另外,咱们删除了ReactTestUtils.SimulateNative的helper方法。他们从未被记录,没有按照他们名字所暗示的那样去作,也没有处理咱们对事件系统所作的更改。若是你想要一种简便的方式来触发测试中原生浏览器的事件,请改用 React Testing Library

安装

咱们鼓励你尽快尝试React 17.0 RC版本,在迁移过程当中遇到任何问题均可以向咱们提出请注意,候选版本没有稳定版本稳定,所以请不要将其部署到生产环境。

经过 npm 安装 React 17 RC 版,请执行:

npm install react@17.0.0-rc.0 react-dom@17.0.0-rc.0

经过 yarn 安装 React 17 RC 版,请执行:

yarn add react@17.0.0-rc.0 react-dom@17.0.0-rc.0

咱们还经过CDN提供了React RC的UMD构建版本:

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

有关详细安装说明,请参阅文档

相关文章
相关标签/搜索