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.它允许任何人自由地使用.修改和重发布,无论现在还是将来.它由一个强大的社群开发,这个社群的 ...
随机推荐
- three.js 数学方法之Matrix3
今天郭先生来说一说three.js的三维矩阵,这块知识需要结合线性代数的一些知识,毕业时间有点长,线性代数的知识大部分都还给了老师.于是一起简单的复习了一下.所有的计算都是使用列优先顺序进行的.然而, ...
- Java基础之java8新特性(1)Lambda
一.接口的默认方法.static方法.default方法. 1.接口的默认方法 在Java8之前,Java中接口里面的默认方法都是public abstract 修饰的抽象方法,抽象方法并没有方法实体 ...
- 如何利用tox打造自动自动化测试框架,看完就懂
什么是toxtox官方文档的第一句话 standardize testing in Python,意思就是说标准化python中的测试,那是不是很适合测试人员来使用呢,我们来看看他究竟是什么? 根据官 ...
- Fortify Audit Workbench 笔记 Privacy Violation: Heap Inspection 隐私泄露(堆检查)
Privacy Violation: Heap Inspection 隐私泄露(堆检查) Abstract 将敏感数据存储在 String 对象中使系统无法从内存中可靠地清除数据. Explanati ...
- 2020HDU多校第三场 1005 Little W and Contest
Little W and Contest Time Limit: 2000/2000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/O ...
- 五天一体_企业权限管理(SSM整合)
学于黑马程序员和传智播客联合做的教学项目 感谢 黑马程序员官网 传智播客官网 个人根据教程的每天的工作进度的代码和资料 密码:cti5 b站在线视频 微信搜索"艺术行者",关注并回 ...
- 面试官你好,我已经掌握了MySQL主从配置和读写分离,你看我还有机会吗?
我是风筝,公众号「古时的风筝」,一个简单的程序员鼓励师. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 面试官:我看你简历上写的你们公司数据库是 ...
- Python time gmtime()方法
描述 Python time gmtime() 函数将一个时间戳转换为UTC时区(0时区)的struct_time,高佣联盟 www.cgewang.com 可选的参数sec表示从1970-1-1以来 ...
- PHP is_array() 函数
is_array() 函数用于检测变量是否是一个数组.高佣联盟 www.cgewang.com PHP 版本要求:PHP 4, PHP 5, PHP 7 语法 bool is_array ( mixe ...
- PHP is_finite() 函数
实例 判断一个值是否为有限值: <?phpecho is_finite(2) . "<br>";echo is_finite(log(0)) . "&l ...