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.它允许任何人自由地使用.修改和重发布,无论现在还是将来.它由一个强大的社群开发,这个社群的 ...
随机推荐
- Windows下安装Python 3.X 版本
一. Python下载 Python官方下载地址 演示下载的版本为Python 3.8.3 ,你可以根据自己的选择安装其他版本的Python 二. Python 安装 下载完安装包双击安装时出错(Wi ...
- 玩LOL间歇性卡顿(FPS突然降低又马上恢复)?Windows10间歇性卡顿?
一..问题描述: LOL时:画面突然死掉,不能操作:FPS突然降低,从三位数降到两位数(150 -> 6).我最开始就是从LOL这里观测到的,因为游戏是卡顿最直观.最明显的表现.之后才发现不玩游 ...
- ORACLE_19c用户密码登录失败的问题以及ORA-28040
测试环境19c 本地登录无异常,创建测试用户,电脑Plsql登录提示报错ORA-28040,处理后再次登录提示密码错误,最后重置密码再次登录OK? 通过这个问题再次测试及反思: 1.ORA-28040 ...
- DQL_MySQL
4.DQL(查询数据){SUPER 重点} 4.1DQL (Data Query Language : 数据查询语言) -所有的查询操作: Select 数据库中最核心的语言 create data ...
- 修改alpine Linux的Docker容器的时区
适用对象 使用 Alpine Linux 发行版的 Docker 镜像容器. 仅仅适用于没有安装uclibc的系统. 修改步骤 进入容器命令行 # docker exec -it container_ ...
- 数据结构C语言实现----树
树的基本知识点 树的定义 树的ADT(抽象数据类型) 树的储存结构 二叉树的定义 二叉树的储存结构 遍历二叉树 二叉树的建立 二叉树的ADT typedef struct BiTNode { Elem ...
- Java线程池ThreadPoolExecutor面试总结思维导图速记
优点 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 提高响应速度,当任务到达时,可以不需要等待线程创建就能立即执行. 提高线程的可管理性 类关系 接 Executor 一个无返 ...
- PHP strrpos() 函数
实例 查找 "php" 在字符串中最后一次出现的位置: <?php高佣联盟 www.cgewang.comecho strrpos("I love php, I l ...
- PDO::errorCode
PDO::errorCode — 获取跟数据库句柄上一次操作相关的 SQLSTATE(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 mixed PDO: ...
- linux的文件处理(匹配 正则表达式 egrep awk sed)和系统、核心数据备份
文件处理 1.处理方式 匹配 正则表达式 egrep awk sed 2.文件中的处理字符 \n 新行符 换行 \t 制表符 tab键 缺省8个空格 \b 退格符 backspace键 退格键 ...