React 17 要来了,非常特别的一版

2023-11-07 04:20

本文主要是介绍React 17 要来了,非常特别的一版,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关注「前端向后」微信公众号,你将收获一系列「用心原创」的高质量技术文章,主题包括但不限于前端、Node.js以及服务端技术

写在前面

React 最近发布了v17.0.0-rc.0,距上一个大版本v16.0(发布于 2017/9/27)已经过去近 3 年了

与新特性云集的 React 16及先前的大版本相比,React 17 显得格外特殊——没有新特性

React v17.0 Release Candidate: No New Features

不仅如此,还带上来了 7 个 breaking change……

一.真没有新特性?

React 官方对 v17 的定位是一版技术改造,主要目标是降低后续版本的升级成本

This release is primarily focused on making it easier to upgrade React itself.

因此 v17 只是一个铺垫,并不想发布重大的新特性,而是为了 v18、v19……等后续版本能够更平滑、更快速地升上来:

When React 18 and the next future versions come out, you will now have more options.

但其中有些改造不得不打破向后兼容,于是提出了 v17 这个大版本变更,顺便搭车卸掉两年多积攒的一些历史包袱

二.渐进式升级成为了可能

在 v17 之前,不同版本的 React 无法混用(事件系统会出问题),所以,开发者要么沿用旧版本,要么花大力气整个升级到新版本,甚至一些常年没有需求的长尾模块也要整体适配、回归测试。考虑到开发者的升级适配成本,React 维护团队同样束手束脚,废弃 API 不敢轻易下掉,要么长时间、甚至无休止地维护下去,要么选择放弃那些老旧的应用

而 React 17 提供了一个新的选项——渐进式升级,允许 React 多版本并存,对大型前端应用十分友好,比如弹窗组件、部分路由下的长尾页面可以先不升级,一块一块地平滑过渡到新版本(参考官方 Demo)

P.S.注意,(按需)加载多个版本的 React 存在着不小的性能开销,同样应该慎重考虑

多版本并存与微前端架构

多版本并存、新旧混用的支持让微前端架构所期望的渐进式重构成为了可能:

渐进地升级、更新甚至重写部分前端功能成为了可能

与 React 支持多版本并存、渐进地完成版本升级相比,微前端更在意的是允许不同技术栈并存,平滑地过渡到升级后的架构,解决的是一个更宽的问题

另一方面,当 React 技术栈下多版本混用难题不复存在时,也有必要对微前端进行反思:

  • 一些问题是不是由技术栈自身来解决更为合适?

  • 多技术栈并存是常态还是短期过渡?

  • 对于短期过渡,是否存在更轻量的解决方案?

关于微前端在解决什么问题的更多思考,见Why micro-frontends?

三.7 个 Breaking change

事件委托不再挂到 document 上

之前多版本并存的主要问题在于React 事件系统默认的委托机制,出于性能考虑,React 只会给document挂上事件监听,DOM 事件触发后冒泡到document,React 找到对应的组件,造一个 React 事件(SyntheticEvent)出来,并按组件树模拟一遍事件冒泡(此时原生 DOM 事件早已冒出document了):

react 16 delegation

因此,不同版本的 React 组件嵌套使用时,e.stopPropagation()无法正常工作(两个不同版本的事件系统是独立的,都到document已经太晚了):

If a nested tree has stopped propagation of an event, the outer tree would still receive it.

P.S.实际上,Atom 在早些年就遇到了这个问题

为了解决这个问题,React 17 不再往document上挂事件委托,而是挂到 DOM 容器上

react 17 delegation

例如:

const rootNode = document.getElementById('root');
// 以为 render 为例
ReactDOM.render(<App />, rootNode);
// Portals 也一样
// ReactDOM.createPortal(<App />, rootNode)
// React 16 事件委托(挂到 document 上)
document.addEventListener()
// React 17 事件委托(挂到 DOM container 上)
rootNode.addEventListener()

另一方面,将事件系统从document缩回来,也让 React 更容易与其它技术栈共存(至少在事件机制上少了一些差异)

向浏览器原生事件靠拢

此外,React 事件系统还做了一些小的改动,使之更加贴近浏览器原生事件:

  • onScroll不再冒泡

  • onFocus/onBlur直接采用原生focusin/focusout事件

  • 捕获阶段的事件监听直接采用原生 DOM 事件监听机制

注意,onFocus/onBlur的下层实现方案切换并不影响冒泡,也就是说,React 里的onFocus仍然会冒泡(并且不打算改,认为这个特性很有用)

DOM 事件复用池被废弃

之前出于性能考虑,为了复用 SyntheticEvent,维护了一个事件池,导致 React 事件只在传播过程中可用,之后会立即被回收释放,例如:

<button onClick={(e) => {console.log(e.target.nodeName);// 输出 BUTTON// e.persist();setTimeout(() => {// 报错 Uncaught TypeError: Cannot read property 'nodeName' of nullconsole.log(e.target.nodeName);});}}>Click Me!
</button>

传播过程之外的事件对象上的所有状态会被置为null,除非手动e.persist()(或者直接做值缓存)

React 17 去掉了事件复用机制,因为在现代浏览器下这种性能优化没有意义,反而给开发者带来了困扰

Effect Hook 清理操作改为异步执行

useEffect本身是异步执行的,但其清理工作却是同步执行的(就像 Class 组件的componentWillUnmount同步执行一样),可能会拖慢切 Tab 之类的场景,因此 React 17 改为异步执行清理工作:

useEffect(() => {// This is the effect itself.return () => {// 以前同步执行,React 17之后改为异步执行// This is its cleanup.};
});

同时还纠正了清理函数的执行顺序,按组件树上的顺序来执行(之前并不严格保证顺序)

P.S.对于某些需要同步清理的特殊场景,可换用LayoutEffect Hook

render 返回 undefined 报错

React 里 render 返回undefined会报错:

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

初衷是为了把忘写return的常见错误提示出来

function Button() {// We forgot to write return, so this component returns undefined.// React surfaces this as an error instead of ignoring it.<button />;
}

在后来的迭代中却没对forwardRefmemo加以检查,在 React 17 补上了。之后无论类组件、函数式组件,还是forwardRefmemo等期望返回 React 组件的地方都会检查undefined

P.S.空组件可返回null,不会引发报错

报错信息透出组件“调用栈”

React 16 起,遇到 Error 能够透出组件的“调用栈”,辅助定位问题,但比起 JavaScript 的错误栈还有不小的差距,体现在:

  • 缺少源码位置(文件名、行列号等),Console 里无法点击跳转到到出错的地方

  • 无法在生产环境中使用(displayName被压坏了)

React 17 采用了一种新的组件栈生成机制,能够达到媲美 JavaScript 原生错误栈的效果(跳转到源码),并且同样适用于生产环境,大致思路是在 Error 发生时重建组件栈,在每个组件内部引发一个临时错误(对每个组件类型做一次),再从error.stack提取出关键信息构造组件栈:

var prefix;
// 构造div等内置组件的“调用栈”
function describeBuiltInComponentFrame(name, source, ownerFn) {if (prefix === undefined) {// Extract the VM specific prefix used by each line.try {throw Error();} catch (x) {var match = x.stack.trim().match(/\n( *(at )?)/);prefix = match && match[1] || '';}} // We use the prefix to ensure our stacks line up with native stack frames.return '\n' + prefix + name;
}
// 以及 describeNativeComponentFrame 用来构造 Class、函数式组件的“调用栈”
// ...太长,不贴了,有兴趣看源码

因为组件栈是直接从 JavaScript 原生错误栈生成的,所以能够点击跳回源码、在生产环境也能按 sourcemap 还原回来

P.S.重建组件栈的过程中会重新执行 render,以及 Class 组件的构造函数,这部分属于 Breaking change

P.S.关于重建组件栈的更多信息,见Build Component Stacks from Native Stack Frames、以及react/packages/shared/ReactComponentStackFrame.js

部分暴露出来的私有 API 被删除

React 17 删除了一些私有 API,大多是当初暴露给React Native for Web使用的,目前 React Native for Web 新版本已经不再依赖这些 API

另外,修改事件系统时还顺手删除了ReactTestUtils.SimulateNative工具方法,因为其行为与语义不符,建议换用React Testing Library

四.总结

总之,React 17 是一个铺垫,这个版本的核心目标是让 React 能够渐进地升级,因此最大的变化是允许多版本混用,为将来新特性的平稳落地做好准备

We’ve postponed other changes until after React 17. The goal of this release is to enable gradual upgrades.

参考资料

  • React v17.0 Release Candidate: No New Features

联系我      

如果心中仍有疑问,请查看原文并留下评论噢。(特别要紧的问题,可以直接微信联系 ayqywx )

这篇关于React 17 要来了,非常特别的一版的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/361231

相关文章

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

Vue ElementUI中Upload组件批量上传的实现代码

《VueElementUI中Upload组件批量上传的实现代码》ElementUI中Upload组件批量上传通过获取upload组件的DOM、文件、上传地址和数据,封装uploadFiles方法,使... ElementUI中Upload组件如何批量上传首先就是upload组件 <el-upl

前端知识点之Javascript选择输入框confirm用法

《前端知识点之Javascript选择输入框confirm用法》:本文主要介绍JavaScript中的confirm方法的基本用法、功能特点、注意事项及常见用途,文中通过代码介绍的非常详细,对大家... 目录1. 基本用法2. 功能特点①阻塞行为:confirm 对话框会阻塞脚本的执行,直到用户作出选择。②

如何使用CSS3实现波浪式图片墙

《如何使用CSS3实现波浪式图片墙》:本文主要介绍了如何使用CSS3的transform属性和动画技巧实现波浪式图片墙,通过设置图片的垂直偏移量,并使用动画使其周期性地改变位置,可以创建出动态且具有波浪效果的图片墙,同时,还强调了响应式设计的重要性,以确保图片墙在不同设备上都能良好显示,详细内容请阅读本文,希望能对你有所帮助...

CSS3 最强二维布局系统之Grid 网格布局

《CSS3最强二维布局系统之Grid网格布局》CS3的Grid网格布局是目前最强的二维布局系统,可以同时对列和行进行处理,将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,本文介... 深入学习 css3 目前最强大的布局系统 Grid 网格布局Grid 网格布局的基本认识Grid 网

HTML5中下拉框<select>标签的属性和样式详解

《HTML5中下拉框<select>标签的属性和样式详解》在HTML5中,下拉框(select标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中选择值的方式,本文将深入探讨select标签的... 在html5中,下拉框(<select>标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中

前端 CSS 动态设置样式::class、:style 等技巧(推荐)

《前端CSS动态设置样式::class、:style等技巧(推荐)》:本文主要介绍了Vue.js中动态绑定类名和内联样式的两种方法:对象语法和数组语法,通过对象语法,可以根据条件动态切换类名或样式;通过数组语法,可以同时绑定多个类名或样式,此外,还可以结合计算属性来生成复杂的类名或样式对象,详细内容请阅读本文,希望能对你有所帮助...

禁止HTML页面滚动的操作方法

《禁止HTML页面滚动的操作方法》:本文主要介绍了三种禁止HTML页面滚动的方法:通过CSS的overflow属性、使用JavaScript的滚动事件监听器以及使用CSS的position:fixed属性,每种方法都有其适用场景和优缺点,详细内容请阅读本文,希望能对你有所帮助... 在前端开发中,禁止htm

Vue3中的动态组件详解

《Vue3中的动态组件详解》本文介绍了Vue3中的动态组件,通过`component:is=动态组件名或组件对象/component`来实现根据条件动态渲染不同的组件,此外,还提到了使用`markRa... 目录vue3动态组件动态组件的基本使用第一种写法第二种写法性能优化解决方法总结Vue3动态组件动态

spring-boot-starter-thymeleaf加载外部html文件方式

《spring-boot-starter-thymeleaf加载外部html文件方式》本文介绍了在SpringMVC中使用Thymeleaf模板引擎加载外部HTML文件的方法,以及在SpringBoo... 目录1.Thymeleaf介绍2.springboot使用thymeleaf2.1.引入spring