今天,我们发布了Reaction 17的第一个候选版本,距离Reaction的上一个主要版本已经过去了两年半,即使按照我们的标准,这也是很长的一段时间了!在这篇博客文章中,我们将描述这个主要版本的作用,您可以期待它有哪些变化,以及您可以如何尝试这个版本。
REACTION 17版本不同寻常,因为它没有添加任何面向开发人员的新功能。相反,此版本主要侧重于简化Reaction本身的升级。
我们正在积极开发新的Reaction功能,但它们不是此版本的一部分。REPACTION 17版本是我们在不落下任何人的情况下推出它们的战略的关键部分。
具体地说,Reaction 17是使将由一个版本Reaction管理的树嵌入由不同版本的React管理的树内变得更安全的“垫脚石”版本。
在过去的七年里,REACT的升级一直是“要么全有,要么什么都不做”。要么继续使用旧版本,要么将整个应用程序升级到新版本。没有中间环节。
到目前为止,这已经奏效了,但我们正在遇到“要么全有要么全无”升级战略的极限。某些API更改(例如,弃用传统上下文API)不可能以自动方式进行。尽管今天编写的大多数应用程序都不使用它们,但我们仍然在Reaction中支持它们。我们必须做出选择,要么在Reaction中无限期地支持它们,要么让一些应用程序留在旧版本的Reaction上。这两个选项并不都很好。
Reaction 17支持逐步的Reaction升级。当你从RECTION 15升级到16(或者很快从REACTION 16升级到17)时,你通常会一次升级整个应用。这对许多应用程序都很有效。但是,如果代码库是在几年前编写的,并且没有积极维护,那么它可能会变得越来越有挑战性。虽然可以在页面上使用两个版本的Reaction,但在Reaction 17之前,这是脆弱的,并且会导致事件问题。
我们正在用Reaction 17解决其中的许多问题,这意味着当Reaction 18和下一个未来版本发布时,你现在将有更多的选择。第一个选择是一次性升级你的整个应用程序,就像你以前可能做的那样。但您也可以选择逐个升级您的应用程序。例如,您可能决定将应用程序的大部分迁移到REACTION 18,但在REACTION 17上保留一些延迟加载的对话框或子路由。
这并不意味着你必须逐步升级。对于大多数应用程序来说,一次性升级仍然是最好的解决方案。加载两个版本的Reaction-即使其中一个是按需延迟加载的-仍然不理想。然而,对于没有积极维护的大型应用程序,这个选项可能是有意义的,并且响应17可以使这些应用程序不会被抛在后面。
要启用渐进式更新,我们需要对Reaction事件系统进行一些更改。Reaction 17是一个主要版本,因为这些更改可能会造成破坏。实际上,我们只需要更换100,000多个组件中的不到20个,所以我们预计大多数应用程序都可以毫不费力地升级到17个。如果你遇到问题请告诉我们。
我们已经准备了一个示例存储库,演示如何在必要时延迟加载较旧版本的Reaction。本演示使用create Reaction App,但是应该可以使用任何其他工具遵循类似的方法。我们欢迎使用其他工具作为拉式请求的演示。
我们已经将其他更改推迟到Reaction 17之后。此版本的目标是实现逐步升级。如果升级到Reaction 17太难了,那就违背了它的目的。
从技术上讲,总是可以嵌套使用不同版本的Reaction开发的应用程序。然而,由于反应事件系统的工作方式,它相当脆弱。
但是,对于大多数事件,Reaction实际上不会将它们附加到声明它们的DOM节点。相反,Reaction直接在文档节点为每个事件类型附加一个处理程序。这称为事件委托。除了它在大型应用程序树上的性能优势之外,它还使添加重放事件等新功能变得更容易。
自首次发布以来,Reaction一直在自动执行事件委派。当DOM事件在文档上触发时,Reaction会确定要调用哪个组件,然后Reaction事件会向上“冒泡”您的组件。但是在幕后,本机事件已经冒泡到了文档级,React在文档级安装了它的事件处理程序。
如果页面上有多个Reaction版本,则它们都会在顶部注册事件处理程序。这会破坏e.stopPropagation():如果嵌套的树已经停止了事件的传播,外部树仍然会接收它。这使得嵌套不同版本的Reaction变得困难。这种担忧不是假设的-例如,Atom编辑器在四年前就遇到了这种情况。
这就是为什么我们要改变Reaction在幕后将事件附加到DOM的方式。
在REACTION 17中,REACTION将不再在文档级附加事件处理程序。相反,它会将它们附加到呈现反应树的根DOM容器中:
在REACTION 16和更早版本中,REACTION将对大多数事件执行Document.addEventListener()。REACTION 17将在幕后调用rootNode.addEventListener()。
多亏了这一更改,现在将一个版本管理的Reaction树嵌入到另一个Reaction版本管理的树中更加安全。请注意,要实现这一点,两个版本都需要17或更高版本,这就是为什么升级到RESPECT 17很重要。在某种程度上,REACTION 17是一个“垫脚石”版本,它使得下一步的逐步升级成为可能。
这一改变还使得将Reaction嵌入到使用其他技术构建的应用程序中变得更容易。例如,如果您的应用程序的外层“shell”是用jQuery编写的,但其中较新的代码是用React编写的,那么Reaction代码中的e.stopPropagation()现在会阻止它访问jQuery代码-正如您所预期的那样。这在另一个方向也是有效的。如果您不再喜欢Reaction,并且想要重写您的应用程序-例如,在jQuery中-您可以开始将外层shell从Reaction转换为jQuery,而不会中断事件传播。
我们已经确认,多年来在我们的问题跟踪器上报告的大量与集成Reaction和非Reaction代码相关的问题已经通过新的行为得到了修复。
与任何突破性更改一样,可能需要调整一些代码。在Facebook,我们总共必须调整大约10个模块(数千个模块)才能适应这一变化。
例如,如果使用Docent.addEventListener(...)添加手动DOM侦听器,您可能希望它们捕获所有反应事件。在REACTION 16和更早版本中,即使您在REACT事件处理程序中调用e.stopPropagation(),您的自定义文档侦听器仍然会接收它们,因为本机事件已经处于文档级。使用REACTION 17,传播将停止(根据请求!),因此您的文档处理程序将不会触发:
文档。AddEventListener(';click';,function(){//此自定义处理程序将不再从调用e.stopPropagation()}的Reaction组件接收点击//);
您可以通过将侦听器转换为使用捕获阶段来修复这样的代码。为此,您可以将true作为第三个参数传递给document。addEventListener:
文档。AddEventListener(';click';,function(){//现在此事件处理程序使用捕获阶段,//因此它接收*所有*下面的单击事件!},true);
注意这个策略总体上是如何更具弹性的-例如,它可能会修复代码中在Reaction事件处理程序外部调用e.stopPropagation()时发生的现有错误。换句话说,Reaction 17中的事件传播更接近于常规DOM。
我们已经将17号反应中的突破性变化控制在最低限度。例如,它不会删除以前版本中已弃用的任何方法。然而,它确实包括一些在我们的经验中相对安全的其他突破性更改。总体而言,我们不得不调整100,000多个组件中的不到20个,因为它们。
Reaction onFocus和onBlur事件已经切换到使用幕后原生的Focusin和Focusout事件,这与Reaction的现有行为更加匹配,有时还会提供额外的信息。
反应17从反应中移除“事件池”优化。它不会提高现代浏览器的性能,甚至会让有经验的Reaction用户感到困惑:
函数handleChange(E){setData(data=>;({...data,//这在Reaction 16及更早版本中崩溃:text:e.target.value}));}
这是因为React重用了不同事件之间的事件对象以提高旧浏览器的性能,并将它们之间的所有事件字段设置为空。对于REACTION 16和更早版本,您必须调用e.Persistent()才能正确使用事件,或者读取前面需要的属性。
在Reaction 17中,此代码的工作方式与您的预期不谋而合。旧的事件池优化已完全删除,因此您可以在需要时读取事件字段。
这是一种行为改变,这就是为什么我们将其标记为破坏,但在实践中,我们在Facebook还没有看到它破坏任何东西。(也许它甚至修复了一些错误!)。请注意,在Reaction事件对象上仍然可以使用e.Persistent(),但是现在它不做任何事情。
UseEffect(()=>;{//这是效果本身。Return()=>;{//这是它的清理。};});
大多数效果不需要延迟屏幕更新,因此Reaction在更新反映在屏幕上后不久就异步运行它们。(对于需要效果来阻止绘制的极少数情况,例如测量和定位工具提示,请首选使用LayoutEffect。)。
然而,Effect Cleanup功能(如果存在)通常会在React 16中同步运行。我们发现,类似于ComponentWillUnmount在类中同步运行,这对于较大的应用程序并不理想,因为它会减慢大屏幕转换(例如切换选项卡)的速度。
在Reaction 17中,效果清理功能也是异步运行的-例如,如果组件正在卸载,则清理将在屏幕更新后运行。
这反映了效果本身是如何更紧密地运行的。在可能需要依赖同步执行的极少数情况下,您可以改为使用LayoutEffect。
另外,根据效果在树中的位置,反应17按照与效果相同的顺序执行清除功能。此前,这一顺序偶尔会有所不同。
虽然可重用的库可能需要对其进行更彻底的测试,但我们只看到几个组件与此更改发生了冲突。有问题的代码的一个示例可能如下所示:
问题是,某些Ref.current是可变的,所以在Cleanup函数运行时,它可能已经设置为NULL。解决方案是捕获效果内部的任何可变值:
我们并不认为这是一个常见的问题,因为我们的eslint-plugin-action-hooks/expletive-deps lint规则(确保您使用它!)。一直对此发出警告。
Function Button(){//我们忘了写Return,因此此组件返回未定义的。//Reaction将其表示为错误,而不是忽略它。;}。
以前,Reaction只对类和函数组件执行此操作,但不检查ForwardRef和Memo组件的返回值。这是由于编码错误造成的。
在REACTION 17中,ForwardRef和Memo组件的行为与常规函数和类组件一致。从它们返回未定义是错误的。
Let Button=ForwardRef(()=>;{//我们忘了写Return,所以这个组件返回UNDEFINED。//Reaction 17将此视为错误,而不是忽略它。;});let Button=memo(()=>;{//我们忘了写Return,所以这个组件返回UNDEFINED。//Reaction 17将此视为错误,而不是忽略它。;});
当您在浏览器中抛出错误时,浏览器会给出一个堆栈跟踪,其中包含JavaScript函数名称及其位置。但是,JavaScript堆栈通常不足以诊断问题,因为反应树层次结构可能同样重要。您不仅想知道Button抛出了错误,还想知道该Button在反应树中的什么位置。
为了解决这个问题,当出现错误时,REACTION 16开始打印“组件堆栈”。尽管如此,它们过去还是不如原生JavaScript堆栈。具体地说,它们在控制台中是不可点击的,因为Reaction不知道函数在源代码中声明的位置。此外,它们在生产中几乎毫无用处。与常规的缩小JavaScript堆栈不同,后者可以通过源地图自动恢复为原始函数名称,而使用Reaction组件堆栈,您必须在生产堆栈和包大小之间进行选择。
在REACTION 17中,使用不同的机制生成组件堆栈,该机制将组件堆栈从常规本地JavaScript堆栈缝合在一起。这使您可以在生产环境中获得完全符号化的Reaction组件堆栈跟踪。
Reaction实现这一点的方式有点非正统。目前,浏览器不提供获取函数堆栈帧(源文件和位置)的方法。因此,当React捕获到错误时,如果可能的话,它现在将通过从上面的每个组件内部抛出(并捕获)一个临时错误来重新构建其组件堆栈。这为崩溃增加了很小的性能损失,但对于每个组件类型只发生一次。
如果您很好奇,可以在这个Pull请求中阅读更多细节,但在大多数情况下,这种确切的机制应该不会影响您的代码。从您的角度来看,新的特性是现在可以单击组件堆栈(因为它们依赖于本机浏览器堆栈帧),并且您可以像处理常规JavaScript错误一样在生产中对它们进行解码。
构成突破性更改的部分是,为了使其正常工作,Reaction在捕获错误之后重新执行堆栈中上面的一些Reaction函数和Reaction类构造函数的一部分。由于呈现函数和类构造函数不应该有副作用(这对于服务器呈现也很重要),这应该不会带来任何实际问题。
最后,最后一个值得注意的突破性变化是,我们删除了一些以前公开给其他项目使用的Reaction内部组件。特别是,Reaction Native for Web过去依赖于事件系统的某些内部结构,但这种依赖关系很脆弱,并被用来打破这种依赖关系。
在第17号反应中,这些私人出口已被取消。据我们所知,REACTION Native for Web是唯一使用它们的项目,而且他们已经完成了向不依赖这些私人输出的不同方法的迁移。
这意味着旧版本的REACTION Native for Web不能与REACTION 17兼容,但新版本可以与之兼容。在实践中,这不会有太大改变,因为Reaction Native for Web无论如何都必须发布新版本来适应内部的Reaction更改。
此外,我们还删除了ReactTestUtils.SimulateNative帮助器方法。它们从来没有被记录在案,没有完全按照它们的名字来做,也没有使用我们对事件系统所做的更改。如果您想要一种在测试中激发本机浏览器事件的便捷方式,请查看Reaction测试库。
我们鼓励您尽快尝试使用RESPECT 17.0候选版本,并针对迁移过程中可能遇到的问题提出任何问题。请记住,与稳定版本相比,候选版本更有可能包含错误,因此不要将其部署到生产环境中。
停止公开Reaction Native Web不需要的内部内容。(@#18483中的Necolas)。
在DEV模式双渲染的第二个渲染过程中禁用控制台。(#18547中的@sebmarkpage)。
禁用<;div隐藏/>;预渲染以支持不同的未来API。(#18917中的@acdlite)。
修复导致挂起树中的更新丢失的错误。(#18384和#18457中的@acdlite)。
修复可能导致更新丢失的输入错误。(#18515中的@jddxf和#18535中的@acdlite)
如果要补水,不要剪掉悬挂表的尾巴。(#18854中的@sebmarkpage)