当前位置: 首页 > 科技观察

React17终于发布了RC版本,官方说17是过渡版!

时间:2023-03-19 20:42:40 科技观察

前言Vue3.0半个月前刚刚发布了rc版本,紧接着React发布了rc版本。不过相比于Vue3在Vue2.x能力上的巨大提升,React17似乎并没有对React16.x有什么非常强大的更新。GitHub上的reactjs/reactjs.org文档中甚至有这么一句话:Therearenonewfeatures!今年React有点瘦??了!那么它更新了什么?翻译一下这个文档:翻译文档地址:github.com/reactjs/rea...题名作者React17.0:无新特性gaearonrachelnabors今天,我们发布了Reactv17的第一个RC版本。自React的上一个主要版本以来已经过去了两年半,按照我们的标准,这已经是很长一段时间了!在这篇博文中,我们将解释这个主要版本对您有何影响以及如何试用。没有新功能React17的不同寻常之处在于它没有添加任何面向开发人员的新功能,而是主要专注于升级和简化React本身。我们正在为React积极开发新功能,但它们不是此版本的一部分。React17是我们深度推广策略的关键。这个版本之所以特别,是因为你可以把React17看作是一个过渡版本,这样可以更安全地将一个React版本管理树嵌入到另一个React版本管理树中。逐步升级在过去的七年里,React一直遵循“要么全有要么全无”的升级策略。您可以继续使用旧版本,也可以将整个应用程序升级到新版本。但两者之间没有任何关系。这种方法一直延续到今天,但我们确实遇到了全有或全无升级策略的局限性。许多API更改,例如在弃用遗留上下文API时,无法以自动方式完成。可能今天的大多数应用程序从未使用过它们,但我们仍然选择在React中支持它们。我们必须在无限期支持过时的API或仍然为某些应用程序使用旧版本的React之间做出选择。但是这两种选择都不合适。因此,我们想提供一个替代方案。React17开始支持React版本的逐步升级。从React15迁移到16(或从React16迁移到17)时,一次升级整个应用程序是很常见的。这适用于大多数应用程序。然而,如果代码库是几年前编写的并且没有得到很好的维护,那么升级它就会变得越来越困难。尽管可以在一个页面上使用两个版本的React,但在React17之前,事件问题仍然存在。我们用React17解决了其中的许多问题。这意味着当React18或未来版本问世时,您将有更多选择。首选选项是像以前一样立即升级整个应用程序。但您也可以选择增量升级您的应用程序。例如,您可能将大部分应用程序迁移到React18,但在React17上保留了一些延迟加载的对话框或子路由。但这并不意味着您必须逐步升级。对于大多数应用程序,一次性升级仍然是最好的解决方案。加载两个React版本,即使其中一个是按需延迟加载,仍然不理想。但是,对于没有主动维护的大型应用,可以考虑这种方案,React17可以保证这些应用不掉队。为了实现升级,我们需要对React的事件系统做一些改动。这些更改会对代码产生影响,这就是为什么React17是一个主要版本。实际上,100,000多个组件中受影响的组件不超过20个,因此我们预计大多数应用程序能够升级到React17而不会受到太大影响。如果您遇到问题,可以联系我们。逐步升级示例我们准备了一个示例(GitHub)存储库,展示了如何在必要时延迟加载旧版本的React。此示例使用CreateReactApp来构建它,但类似的方法也适用于其他工具。欢迎开发者使用其他工具编写demo,提交PR。注意:我们已将其他更新推迟到React17之后。此版本的目标是实现逐步升级。如果升级到React17太困难,我们的目标将无法实现。更改事件委托在应用程序中嵌套不同版本的React在技术上总是可行的。但由于React的事件系统的工作方式,这很难做到。在React组件中,事件处理通常内联编写:这段代码的DOM等价物如下:但对于大多数事件,React不会将它们附加到DOM节点。相反,React直接在文档节点上为每个事件类型附加一个处理程序,这称为事件委托。除了在大型应用程序上具有性能优势外,它还可以更轻松地添加新功能,如重播事件。React自发布以来就自动完成了事件委托。当在文档上触发DOM事件时,React会找出调用了哪个组件,然后React事件会将组件“冒泡”。但实际上,原生事件已经从“文档”层面冒出来了,而React就是安装在文档中的事件处理器。但这就是升级的困难所在。如果页面上有多个React版本,它们都会在顶层注册事件处理程序。如果嵌套树阻止事件冒泡,但外部树仍然接收它,这将中断e.stopPropagation()。这会使不同版本的React的嵌套变得非常困难。这种担忧并非没有根据——例如,Atom编辑器在四年前就遭遇过同样的问题。这就是为什么我们需要改变React在底部附加事件的方式。在React17中,React将不再向文档中添加事件处理程序。相反,事件处理程序将附加到呈现的React树的根DOM节点:constrootNode=document.getElementById('root');ReactDOM.render(,rootNode);在React16或更早版本中,大多数事件都会执行Reactdocument.addEventListener()。React17将在后台调用rootNode.addEventListener()。由于这一变化,新旧React树的嵌套现在更加安全。请注意,要使其正常工作,两个版本都必须为17或更高版本,这是强烈建议升级到React17的根本原因。从某种意义上说,React17是一个过渡版本,可以逐步升级。这一变化还使得将React嵌入到使用其他技术构建的应用程序中变得更加容易。例如,如果应用程序的“外壳”是用jQuery编写的,但其中较新的代码是用React编写的,那么React代码中的e.stopPropagation()将阻止它影响jQuery的代码——如您所料。换句话说,如果您不再喜欢React并想重写应用程序(比如在jQuery中),您可以从外部将React转换为jQuery,而不会破坏事件冒泡。经证实,多年来在问题跟踪器上报告的许多问题都已通过新功能得到解决,其中大部分问题与将React与非React代码集成有关。注意:您可能想知道这是否会破坏根容器之外的Portal。答案是React也会监听portals容器上的事件,所以这不是问题。解决陷阱与其他主要更新一样,可能需要调整代码。在Facebook,我们调整了数千个模块中的大约十个以适应此更新。例如,如果模块使用document.addEventListener(...)手动添加DOM侦听器,您可能希望捕获所有React事件。在React16或更早版本中,即使您在React事件处理程序中调用e.stopPropagation(),您创建的DOM侦听器仍会触发,因为本机事件已经在文档级别。使用React17冒泡会被阻塞(按需),所以你的文档级事件监听器不会触发:document.addEventListener('click',function(){//如果React组件调用e.stopPropagation()//then此自定义侦听器函数将不会接收点击事件});您可以将侦听器转换为使用事件捕获来修复此类代码。为此,可以将{capture:true}作为document.addEventListener的第三个参数传递:可以接收!},{capture:true});请注意,此策略在全球范围内更具适应性。例如,它可能会修复代码中在React事件处理程序外部调用e.stopPropagation()时发生的现有错误。换句话说,React17的事件冒泡更接近常规DOM。其他重大更改我们将React17中的重大更改保持在最低限度。例如,它不会删除以前版本中弃用的任务方法。但是,它确实包含一些其他重大更改,根据经验,这些更改应该相对安全。总的来说,由于这些因素的存在,十万多个组件中受影响的组件不超过二十个。我们对基准浏览器的事件系统进行了一些小更新:onScroll事件不再冒泡以防止出现一些混乱。React的onFocus和onBlur事件已经切换为底层的原生focusin和focusout事件。它们更接近React的现有行为,有时会提供额外的信息。捕获事件(例如,onClickCapture)现在在实际浏览器中使用捕获侦听器。这些变化使React更接近浏览器行为并提高了互操作性。注意:尽管将焦点事件从React17切换为focusin,但onFocus不会影响冒泡行为。在React中,onFocus事件总是冒泡,并且它在React17中继续冒泡,因为它通常是一个更有用的默认值。查看此沙箱以了解可以为不同的特定用例添加的不同检查。React17中移除了事件池“eventpooling”。它并没有提升现代浏览器的性能,甚至让有经验的开发者感到困惑:functionhandleChange(e){setData(data=>({...data,//ThiscrashesinReact16andearlier:text:e.target.value}));}这是因为React为旧浏览器中的不同事件重用了事件对象,以提高性能并在它们之前将所有事件字段设置为null。在React16及更早版本中,用户必须调用e.persist()才能正确使用事件,或者正确读取所需的属性。在React17中,此代码按预期工作。旧的事件池优化已被删除,因此消费者可以在需要时读取事件字段。这改变了行为,因此我们将其标记为重大更新,但实际上我们没有看到它对Facebook产生影响(甚至修复了一些错误!)。请注意,e.persist()在React事件对象上仍然可用,只是没有效果。副作用清理时间我们正在调整useEffect和清理函数的时间。useEffect(()=>{//Thisistheeffectitself.return()=>{//Thisisitscleanup.};});大多数副作用(effect)不需要延迟刷新视图,所以React会在更新反映到屏幕上后立即异步执行它们(在极少数情况下您需要副作用来防止重绘。例如,如果使用useLayoutEffect你需要获得大小和位置)。然而,副作用清理功能(如果存在)在React16中同步运行。我们发现这对于大型应用程序来说并不理想,因为同步会减慢视图的更新速度(例如切换选项卡)。在React17中,副作用清理函数是异步执行的——如果组件被卸载,清理将在视图更新后运行。这反映了副作用本身如何更紧密地发挥作用。在极少数情况下,您可能希望依赖同步执行,您可以改用useLayoutEffect。注意:您可能想知道这是否意味着您现在将无法修复有关未安装组件上的setState的警告。别担心,React会专门处理这种情况,不会在卸载和清理之间的短暂间隔内发出有关setState的警告。因此,取消代码的请求或间隔几乎总是可以保持不变。此外,React17将根据效果在树中的位置,按照与效果相同的顺序执行清理。在以前,顺序有时会有所不同。潜在危害可重用库可能需要针对这种情况进行深入测试,但我们只遇到了一些会因该问题而中断执行的组件。例如:useEffect(()=>{someRef.current.someSetupMethod();return()=>{someRef.current.someCleanupMethod();};});问题在于someRef.current是可变的,因此在运行清理函数时,它可能已设置为null。解决方案是将值存储在将更改的效果中:useEffect(()=>{constinstance=someRef.current;instance.someSetupMethod();return()=>{instance.someCleanupMethod();};});我们不希望这个问题影响到所有人,我们为eslint-plugin-react-hooks/exhaustive-deps提供的lint插件(确保在你的项目中使用它)会警告这种情况。返回一致的undefined错误在React16及更早的版本中,返回undefined总会报错:functionButton(){return;//Error:Nothingwasreturnedfromrender}很容易无意中返回undefined:functionButton(){//忘记了在这里写ruturn,所以这个组件返回一个未定义的。//React会抛出一个错误而不是忽略它。