写在后面
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 组件嵌套应用时,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 容器上:
例如:
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 v17.0 Release Candidate: No New Features
有所得、有所惑,真好
关注「前端向后」微信公众号,你将播种一系列「用 心原创」的高质量技术文章,主题包含但不限于前端、Node.js 以及服务端技术
本文首发于 ayqy.net,原文链接:http://www.ayqy.net/blog/reac…