React 17 要来了,非常特别的一版
写在前面
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
了):
[caption id="attachment_2263" align="alignnone" width="625"] react 16 delegation[/caption]
因此,不同版本的 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 容器上:
[caption id="attachment_2264" align="alignnone" width="625"] react 17 delegation[/caption]
例如:
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 null
console.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 />;
}
在后来的迭代中却没对forwardRef
、memo
加以检查,在 React 17 补上了。之后无论类组件、函数式组件,还是forwardRef
、memo
等期望返回 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 17 要来了,非常特别的一版的更多相关文章
- 蒲公英 · JELLY技术周刊 Vol 27: 平平无奇 React 17
蒲公英 · JELLY技术周刊 Vol.27 这个热闹的十月终于要走到尾声,React 17 历经 4 个 RC 版本之后,也于数天前正式发布了,而同在几天前发布的 CRA 4.0 也已经完成了 Re ...
- React 17 All In One
React 17 All In One v17.0.1 https://reactjs.org/blog/2020/10/20/react-v17.html https://reactjs.org/b ...
- React 17 发布候选版本, 没有添加新功能
React 17 发布候选版本, 没有添加新功能 React v17.0 Release Candidate: No New Features https://reactjs.org/blog/202 ...
- 前端用Webpact打包React后端Node+Express实现简单留言版
前言 React官方推荐用Browserify或者Webpack 来开发React组件. Webpack 是什么?是德国开发者 Tobias Koppers 开发的模块加载器.Instagram 工程 ...
- React+Redux实现追书神器网页版
引言 由于现在做的react-native项目没有使用到redux等框架,写了一段时间想深入学习react,有个想法想做个demo练手下,那时候其实还没想好要做哪一个类型的,也看了些动漫的,小说阅读, ...
- react 组件的生命周期 超简版
组件从被创建到被销毁的过程称为组件的 生命周期: 通常,组件的生命周期可以被分为三个阶段:挂载阶段.更新阶段.卸载阶段: 一.挂载阶段 这个阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件的第 ...
- 设计模式-(17)策略模式 (swift版)
一,概念: 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化. 二,使用场景 1.针对同一类型问题的多种处理方式,仅仅是具体行为有差别时: ...
- Facebook 开源安卓版 React Native,开发者可将相同代码用于网页和 iOS 应用开发
转自:http://mt.sohu.com/20150915/n421177212.shtml Facebook 创建了React Java 库,这样,Facebook 的工程团队就可以用相同的代码给 ...
- Linux系统各发行版镜像下载(2)
Fedora ISO镜像下载: Fedora 是一个开放的.创新的.前瞻性的操作系统和平台,基于 Linux.它允许任何人自由地使用.修改和重发布,无论现在还是将来.它由一个强大的社群开发,这个社群的 ...
随机推荐
- 用x种方式求第n项斐波那契数,99%的人只会第一种
大家好啊,我们又见面了.听说有人想学数据结构与算法却不知道从何下手?那你就认真看完本篇文章,或许能从中找到方法与技巧. 本期我们就从斐波那契数列的几种解法入手,感受算法的强大与奥妙吧. 原文链 ...
- JVM系列之:Contend注解和false-sharing
目录 简介 false-sharing的由来 怎么解决? 使用JOL分析 Contended在JDK9中的问题 padded和unpadded性能对比 Contended在JDK中的使用 总结 简介 ...
- 本地ES集群数据通过_reindex方式迁移到腾讯云服务器(亲测有效)
本地ES集群数据通过_reindex方式迁移到腾讯云服务器(亲测有效) 随着业务量的增加,本地的ES集群服务器性能和磁盘空间有点不够使用,项目组考虑使用腾讯云服务器,以下是我测试的使用_reindex ...
- 如何获取论文的 idea
知乎上有一个提问"计算机视觉领域如何从别人的论文里获取自己的idea?" 非常有意思,这里也总结下: Cheng Li的回答:找40篇比较新的paper,最好是开源的.你能看懂的. ...
- Bug--Tomcat Error start child
添加Quartz之后报错 下面的Cause by: More than one fragment with the name [spring_web] was found. This is not l ...
- IO流——字节流、字符流
在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 流的分类 ① 流按其流向分为“输入流”和“输出 ...
- MacOS SVN简单入门
背景:MacOS内置了SVN的客户端和服务器端的软件,下边所使用到的目录需要结合自己电脑的具体情况进行设置,并不是很困难. MacOS SVN简单入门 第一部分,创建本地的SVN测试仓库,并修改相应的 ...
- 将形如 5D, 30s 的字符串转为秒
import sys def convert_to_seconds(time_str): # write code here if 's' in time_str: return float(time ...
- 第3章 探索性数据分析(单因子&对比)与可视化
1.理论铺垫 Dataframe和Series均适用 ~集中趋势:均值mean().中位数median().与分位数quantile(q=0.25).众数mode() ~离中趋势:标准差std().方 ...
- 11-13 模块_collections(不太重要)&time&random&os
random:随机数模块 os:和操作系统打交道的模块 sys:和Python解释器打交道的模块 序列化模块:Python中的数据类型和str转换的模块 http://www.cnblogs.com/ ...