写在后面

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 />;}

在起初的迭代中却没对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

有所得、有所惑,真好

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

本文首发于 ayqy.net ,原文链接:http://www.ayqy.net/blog/reac...