React v17 来了

简介: React v17 来了

640.jpg写在前面


半个月前,React发布了v17的第一个RC版本。距离上一个Major版本发布已经快两年半了。最近事情比较多,这周才有时间来“观察”一下v17版本。话不多少,让我们来看看这个版本中,包含了哪些内容呢?


新版本没有新特性


React v17中不会有用户可见的新特性。React v17是一个过渡版本,其存在的意义是为了让React的更新变得更容易一些。大家比较期待的Concurrent 模式并没有发布。


渐进式更新


在以前版本的React中,React采用的是一种"all-or-nothing"的更新策略。要么你完全使用一个新的版本,要么完全使用老版本,中间没有缓冲。这种更新策略会造成一定的问题。比如context api。如果你维护的一个老项目使用的是老版本的React,并且大量使用了context api,那么升级React版本将会是比较困难的。需求压得紧,老板不给你排期的情况下,你不得不继续使用老版本。

React v17将支持渐进式更新策略。说白了,从React v17后,如果你在项目中同时使用两个版本的React时,将不会出现任何问题。这将更有利于进行老项目的迁移和升级。官方还给出了一个渐进式更新的的例子:https://github.com/reactjs/react-gradual-upgrade-demo/


事件系统更新


尽管 React v17 没有带来用户可见的更新,但其实其内部的机制是有更新的。比如,事件系统就进行了调整。

我们都知道,React有自己的一套事件代理系统。比如我们的jsx中有以下代码:


<button onClick={handleClick}>点我,点我</div>


在代码中,我们是将点击函数绑定在了button上,但其实React是会进行事件代理的。最终的点击函数会被绑定在document上。React之所以这样做,是考虑了性能问题。这个不难理解。但是这样会影响到React的渐进式更新策略。当页面中包含了多个版本的React时,就会出现问题:假设react v16的组件包含了一个react v15的组件,事件处理函数会在document上绑定两次,那么e.stopPropagation()就失效了,最终外层的DOM依然会收到事件。


于是,React更新了事件代理策略:事件处理函数不再绑定在document上,而是绑定在React组件树的根DOM上


640.png


这样,就可以更安全的进行多React版本的组件树的嵌套。并且,React嵌入使用其他技术构建的应用程序变得更加容易。

不过,本次事件系统的更新,有以下场景需要注意。


如果你手动在document上绑定了事件函数,在React v16 及以前,如果你在组件中绑定的事件函数中调用了e.stopPropagation(),那么你在document中绑定的事件函数依然会收到原生事件,因为原生事件本身已经在document这一层了。在React v17中,这种情况将不会发生。


document.addEventListener('click', function() {  // v17中,原生的事件处理函数不再收到事件,如果你在React组件中调用了 e.stopPropagation()  // v16及以下,会。});


要修正这个问题,可以在事件捕获阶段进行事件处理。


document.addEventListener('click', function() {  // 现在这个事件处理函数使用了事件捕获,  // 所以它可以接收到所有的点击事件!}, { capture: true });


一句话说,新的React事件系统中,事件冒泡更接近原生的DOM事件系统。



其他破坏性更新


React虽然已经将破坏性更新降低到了最少,但还是有少量的修改。


对齐浏览器


React还对事件系统进行了如下的更改:


1、onScroll事件不再冒泡。


2、React的onFocusonBlur事件已在底层切换为原生的focusinfocusout事件。它们更接近React现有行为,有时还会提供额外的信息。


3、捕获事件(例如,onClickCapture)现在使用的是实际浏览器中的捕获监听器。



取消了事件池


React v16及以下,是有一个事件池来专门管理事件的。起初,设计这个事件池,是考虑到了性能。但是在现代浏览器中,并没有对性能有多大提升,反而会让开发者疑惑,考虑下面的代码:


function handleChange(e) {  setData(data => ({    ...data,    // 在react v16及以下版本中,这段代码会出错    text: e.target.value  }));}


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

在React v17中,上述代码将不会出现问题,可以按照预期执行。e.persist()在 React事件对象中仍然可用,只不过没有任何效果罢了。


副作用清理时机


在React v17中,useEffect中的清理函数的调用时机进行了调整。


useEffect(() => {  // 副作用  ...  return () => {    // 副作用清理函数  };});


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

在 React v16中,副作用清理函数是同步执行的。在大型App中,这不太合适,因为这会影响到试图的更新。


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


这个改变可能会造成一些case,比如下面的场景。


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


someRef.current是可变的。同步执行时,上述代码没有问题。但是异步执行时,someRef.current可能已经变成null了。这个时候,就需要在副作用中,保存对可变变量的引用:


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


返回一致的undefined错误


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


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


上述情况其实是很容易发生的


function Button() {  // 忘记return,该组件最终就是undefined  // React会抛出这个错误,而不是忽略它。  <button />;}


在以前,针对上述情况,React只会检查类组件和函数组件,并不会检查forwardRefmemo。在v17中,上述情况也会check。


let Button = forwardRef(() => {  // react 17 会抛出错误  <button />;});
let Button = memo(() => {  // react 17 会抛出错误  <button />;});


原生组件栈


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

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


在React 17中,使用了不同的机制生成组件堆栈,该机制会将它们与常规的原生JavaScript堆栈缝合在一起。这使得你可以在生产环境中获得完全符号化的React组件堆栈信息。


移除私有导出


最后,v17删除了一些以前暴露给其他项目的React内部组件。特别是,React Native for Web过去常常依赖于事件系统的某些内部组件,但这种依赖关系很脆弱且经常被破坏。


React v17删除了这些私有导出。对于大多数的React开发者来说,不会有任何的影响。


写在后面


本文内容完全来自官方blog:https://reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing


当然,这只是官方自己外公开的变化。其内部真正的一些策略变更,架构更新等,这个就需要阅读源码来寻找答案了。而React源码(hook除外)一直是我想吭,但是至今未行动的一座大山。从官方的Blog来看,最可见的更新(面试最可见),应该就是事件系统的更新了。


相关阅读


谈一谈Web中的事件

相关文章
|
6月前
|
Web App开发 前端开发 JavaScript
React 之 requestIdleCallback 来了解一下
React 之 requestIdleCallback 来了解一下
395 0
|
8天前
|
前端开发 JavaScript 数据管理
React 特点
React 是一个用于构建用户界面的JavaScript库,以其声明式设计、高效的DOM操作模拟、高度灵活性和组件化开发而著称。支持JSX语法,推荐使用以提高开发效率。React采用单向数据流,简化了数据管理,适合大型项目开发。
|
3月前
|
前端开发
React 19 CheatSheet
React 19 CheatSheet
|
4月前
|
前端开发 JavaScript 数据管理
React精通之路
【7月更文挑战第6天】构建React精通之路:始于基础(HTML/CSS/JS,React文档),经由环境配置、组件、状态、路由学习;进阶探索Hooks、Redux、性能优化、测试调试;通过实战项目巩固,研究高级技术如HOC;参与开源,关注最新动态,活跃于技术社区,持续学习与成长。
46 0
|
5月前
|
XML 前端开发 JavaScript
什么是react
什么是react
53 4
|
6月前
|
存储 前端开发 JavaScript
对于React的了解与认识
对于React的了解与认识
|
XML 前端开发 JavaScript
react的特点
react的特点
105 0
|
XML 存储 缓存
|
缓存 前端开发 JavaScript
React哲学思想
React是用JavaScript构建快速响应的大型Web应用程序的首选方式。由于前端我们是无法改变加载的网速,但是我们可以通过加入view界面提示加载,这样在响应的过程中不会让用户一直处于空白界面的状态。可以调用React中Lazy&Suspence来实现。如果项目崩溃或者网络崩溃的时候,可以使用ErrorBondary,展示出自己定义渲染的“错误”的UI界面
5312 1
|
前端开发 调度
一篇必看的React文章
本文适合有 React 基础的小伙伴进阶学习
一篇必看的React文章