官方文档
- https://legacy.reactjs.org/blog/2020/10/20/react-v17.html
- https://github.com/facebook/react/releases/tag/v17.0.0
- https://github.com/facebook/react/releases/tag/v17.0.2
历程介绍
React17正式版,共历经三个正式版更新,进入React18阶段,主要目的是为了过渡,渐进式的去升级
版本名称 | 版本类型 | 发布时间 |
---|---|---|
react17 RC版本 | RC版本(准正式版) | 2020年8月11日 |
react17.0.0 | 正式版 | 2020年10月20日 |
react17.0.1 | 正式版 | 2020年10月22日 |
react17.0.2 | 正式版 | 2021年03月22日 |
官方更新内容
来自于github tag的描述
17.0.0
React
-
添加
react/jsx-runtime
和react/jsx-dev-runtime
支持新的 JSX 转换 -
从原生错误帧中构建组件堆栈
- React 17 通过原生错误帧来构建组件堆栈信息。这种改进增强了错误调试的体验,使开发者能够获得更加准确的错误堆栈信息,从而更快定位问题
-
允许为 Context 指定
displayName
以改进堆栈信息- 在 React 17 中,可以为 Context 设置
displayName
,使得在调试时的堆栈信息更易读。这对大型应用中的调试特别有帮助,使得组件树和上下文的关联更加直观
- 在 React 17 中,可以为 Context 设置
-
防止
'use strict'
泄漏到 UMD 包中- React 17 修复了一个问题,防止
'use strict'
作用域意外扩展到 UMD 打包文件中,从而避免影响全局代码。这有助于确保模块在使用不同打包方式时保持一致的行为
- React 17 修复了一个问题,防止
-
停止使用
fb.me
进行重定向- eact 17 停止了对
fb.me
重定向的使用。这一改动是为了减少对 Facebook 专有域的依赖,提供更可靠的文档链接体验
- eact 17 停止了对
React DOM
-
将事件委托到根节点而不是
document
-
运行下一个effects之前,清理所有旧的effects
-
useEffect
的清理函数改为异步运行 -
使用浏览器的
focusin
和focusout
事件来处理onFocus
和onBlur
- 但是onFocus 事件总是冒泡的,在 React17 中会继续保持
-
使所有捕获事件使用浏览器的捕获阶段
-
不再模拟
onScroll
事件的冒泡 -
如果
forwardRef
或memo
组件返回undefined
,则抛出错误 -
移除事件池化
- React 17 移除了事件池化机制
-
停止暴露不必要的内部 API 给 React Native Web
-
在根节点挂载时附加所有已知事件监听器
- 提升了事件监听器的挂载效率,减少了运行时的额外开销
-
在开发模式的第二次渲染传递中禁用控制台console
-
弃用未公开且容易误导的
ReactTestUtils.SimulateNative
API- 因为其行为与语义不符,如果你想要一种简便的方式来触发测试中原生浏览器的事件,可直接使用 React Testing Library
-
重命名内部使用的私有字段
-
在开发模式下不再调用User Timing API
-
在严格模式下双重渲染没有 Hooks 的组件
-
允许在生命周期方法中调用
ReactDOM.flushSync
-
为键盘事件对象添加
code
属性 -
为视频元素添加
disableRemotePlayback
属性- 该属性允许禁用远程播放,提供更细粒度的播放控制
-
为输入元素添加
enterKeyHint
属性-
增加对输入法回车行为的提示。这个属性会影响虚拟键盘的
enter
键的样式和行为,主要用于移动端和平板电脑等设备上,让用户清楚地知道enter
键将执行何种动作。这个属性的典型值有 “enter”, “done”, “go”, “next”, “previous”, “search”, 和 “send” 等。例如,如果你在一个搜索框中使用
enterkeyhint="search"
属性,当用户在移动设备上使用这个搜索框时,enter
键将会变为"搜索",用户点击这个键就能提交搜索。 -
好文推荐:https://cloud.tencent.com/developer/article/2312900
-
-
在
<Context.Provider>
无值时给出警告 -
当
memo
或forwardRef
组件返回undefined
时发出警告 -
改进无效更新的错误信息
-
在堆栈帧中排除
forwardRef
和memo
-
改进切换受控和非受控输入时的错误信息
-
保持
onTouchStart
、onTouchMove
和onWheel
事件的被动性- 保持这些触摸事件为被动事件,优化了滚动和触摸操作的性能,避免了不必要的阻塞
-
修复在关闭的 iframe 中开发模式下
setState
挂起的问题 -
修复懒加载组件在设置
defaultProps
时渲染的退出- 解决了懒加载组件在设置
defaultProps
时可能引起的不渲染问题,确保懒加载组件正确处理默认属性
- 解决了懒加载组件在设置
-
修复当
dangerouslySetInnerHTML
为undefined
时的误报警告 -
修复在非标准
require
实现中的测试工具问题 -
修复
onBeforeInput
报告错误的event.type
-
修复 Firefox 中
event.relatedTarget
被报告为undefined
的问题 -
修复 IE11 中的“不明确错误”问题
-
修复渲染到 Shadow DOM 时的问题
- 解决了在 Shadow DOM 中渲染时遇到的问题,增强了 React 对 Web Components 的支持。
-
为
onSubmit
和onReset
事件使用委托机制 -
改进内存使用
React DOM Server
- 使
useCallback
的行为在服务端渲染器中与useMemo
保持一致 - 修复函数组件抛出错误时的状态泄漏问题
React17更新重点总结
具体来说,React 17 是一个“垫脚石”版本,它使得将由一个版本的 React 管理的树嵌入到由不同版本的 React 管理的树中更加安全
1.移除事件池
-
什么叫事件池呢?
-
事件池:合成事件对象会被放入池中统一管理。这意味着合成事件对象可以被复用,当所有事件处理函数被调用之后,其所有属性都会被回收释放置空。
-
也就说你在React17 之前如果异步的方式直接去获取事件 e 对象显然是行不通的,他会被清除释放掉,所以在React17 之前你需要在异步方法中获取事件 e 得使用e.persist()方法将当前的合成事件从事件池中删除,并允许保留对事件的引用,才可以在异步方法中引用 e
-
但是React17 就没有这个事件池东西了,也就不会去清空释放动作,你可以直接在异步中调用
function App() {
// v17 去除了 React 事件池,异步方式使用e不再需要 e.persist()
const handleClick = (e: React.MouseEvent) => {
console.log(e.target) // <button>React事件池</button>
setTimeout(() => {
// 如果是 v16 返回的是null
console.log(e.target) // <button>React事件池</button>
})
}
return (
<div className="App">
<button onClick={handleClick}>React事件池</button>
</div>
)
}
2.事件委托委托到更节点
-
在 React 16 及更早版本中,React 可以处理
document.addEventListener()
大多数事件。React 17 将rootNode.addEventListener()
在后台进行调用。 -
在 React 17 中,React 将不再在
document
底层附加事件处理程序。相反,它会将它们附加到渲染 React 树的根 DOM 容器中:const rootNode = document.getElementById('root'); ReactDOM.render(<App />, rootNode);
3.onScroll
事件调整
- onScroll 事件不再冒泡
4.onFocus 和 onBlur 事件调整
- React 的 onFocus 和 onBlur 事件已在底层切换为原生的 focusin 和 focusout 事件,但是onFocus 事件总是冒泡的,在 React17 中会继续保持
5.useEffect
的清理函数运行调整
在 React17 之前组件被卸载时,useEffect中清理函数都是同步运行的,而 React17 改为了异步执行
因此有一些点要注意,就是改成异步后一些ref.current的值可能为 null 了,对此要么你在它变为 null 之前先把它用变量存起来,要么用useLayoutEffect,useLayoutEffect可以保证回调函数同步执行,这样就能确保 ref 此时还是最后的值
useLayoutEffect(() => {
demoRef.current.someSetupMethod()
return () => {
demoRef.current.someCleanupMethod()
}
})
6.改进堆栈信息
-
React16 中错误调用栈的缺点:
- 缺少源码位置追溯,在控制台无法点击跳转到到出错的地方,无法适用于生产环境
- 整体来说不如原生的 JavaScript 调用栈,不同于常规压缩后的 JavaScript 调用栈,它们可以通过 sourcemap 的形式自动恢复到原始
函数的位置,而使用 React 组件栈,在生产环境下必须在调用栈信息和 bundle 大小间进行选择
-
React对堆栈错误信息进行了改进
-
从原生错误帧中构建组件堆栈
- React 17 通过原生错误帧来构建组件堆栈信息。这种改进增强了错误调试的体验,使开发者能够获得更加准确的错误堆栈信息,从而更快定位问题
-
允许为 Context 指定
displayName
以改进堆栈信息- 在 React 17 中,可以为 Context 设置
displayName
,使得在调试时的堆栈信息更易读。这对大型应用中的调试特别有帮助,使得组件树和上下文的关联更加直观
- 在 React 17 中,可以为 Context 设置
-
7.移除部分API
- 移除部分给 React Native Web用的API
- 还删除了ReactTestUtils.SimulateNative工具方法,因为其行为与语义不符,如果你想要一种简便的方式来触发测试中原生浏览器的事件,可直接使用 React Testing Library
8.支持新的 JSX 变换
相关官方文档:https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
当你使用 JSX 时,编译器会将其转换为浏览器可以理解的 React 函数调用。旧版 JSX 转换将 JSX 转换为React.createElement(...)
调用。
例如,假设您的源代码如下所示:
import React from 'react';
function App() {
return <h1>Hello World</h1>;
}
在底层,旧的 JSX 转换将其转换为常规 JavaScript:
import React from 'react';
function App() {
return React.createElement('h1', null, 'Hello world');
}
**您的源代码无需进行任何更改。**我们正在描述 JSX 转换如何将您的 JSX 源代码转换为浏览器可以理解的 JavaScript 代码。
但这并不完美:
- 因为 JSX 被编译成了
React.createElement
,所以React
如果使用 JSX 的话就需要在范围内。 - 有一些性能改进和简化是
React.createElement
不允许的。
为了解决这些问题,React 17 在 React 包中引入了两个新的入口点,它们仅供 Babel 和 TypeScript 等编译器使用。新的 JSXReact.createElement
转换不会将 JSX 转换为,而是会自动从 React 包中的新入口点导入特殊函数并调用它们。
假设您的源代码如下所示:
function App() {
return <h1>Hello World</h1>;
}
这是新的 JSX 转换将其编译为的内容:
// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
请注意,我们的原始代码不再需要导入 React即可使用 JSX!(但我们仍然需要导入 React 才能使用 Hooks 或 React 提供的其他导出。)
9.启发式更新算法(Lane模型)
React17增加更新优先级的功能lane模型
优先级使用lane取代expirationTime
https://juejin.cn/post/7248982532728602681
https://juejin.cn/post/7282962960212639805
https://juejin.cn/post/6860275004597239815
- React中用lane(车道)模型来表示任务优先级
- 一共有31条优先级,数字越小优先级越高,某些车道的优先级相同
- clz32函数返回开头的 0 的个数
//一共有16种优先级
//同步优先级
const SyncLanePriority = 15;
const SyncBatchedLanePriority = 14;
//离散事件优先级
const InputDiscreteHydrationLanePriority = 13;
const InputDiscreteLanePriority = 12;
//连续事件优先级
const InputContinuousHydrationLanePriority = 11;
const InputContinuousLanePriority = 10;
//默认优先级
const DefaultHydrationLanePriority = 9;
const DefaultLanePriority = 8;
//渐变优先级
const TransitionHydrationPriority = 7;
const TransitionPriority = 6;
const RetryLanePriority = 5;
const SelectiveHydrationLanePriority = 4;
//空闲优先级
const IdleHydrationLanePriority = 3;
const IdleLanePriority = 2;
//离屏优先级
const OffscreenLanePriority = 1;
//未设置优先级
const NoLanePriority = 0;
/**
* 一共有31条车道
*/
const TotalLanes = 31;
//没有车道,所有位都为0
const NoLanes = 0b0000000000000000000000000000000;
const NoLane = 0b0000000000000000000000000000000;
//同步车道,优先级最高
const SyncLane = 0b0000000000000000000000000000001;
const SyncBatchedLane = 0b0000000000000000000000000000010;
//离散用户交互车道 click
const InputDiscreteHydrationLane = 0b0000000000000000000000000000100;
const InputDiscreteLanes = 0b0000000000000000000000000011000;
//连续交互车道 mousemove
const InputContinuousHydrationLane = 0b0000000000000000000000000100000;
const InputContinuousLanes = 0b0000000000000000000000011000000;
//默认车道
const DefaultHydrationLane = 0b0000000000000000000000100000000;
const DefaultLanes = 0b0000000000000000000111000000000;
//渐变车道
const TransitionHydrationLane = 0b0000000000000000001000000000000;
const TransitionLanes = 0b0000000001111111110000000000000;
//重试车道
const RetryLanes = 0b0000011110000000000000000000000;
const SomeRetryLane = 0b0000010000000000000000000000000;
//选择性水合车道
const SelectiveHydrationLane = 0b0000100000000000000000000000000;
//非空闲车道
const NonIdleLanes = 0b0000111111111111111111111111111;
const IdleHydrationLane = 0b0001000000000000000000000000000;
//空闲车道
const IdleLanes = 0b0110000000000000000000000000000;
//离屏车道
const OffscreenLane = 0b1000000000000000000000000000000;
/**
* 分离出所有比特位中最右边的1
* 分离出最高优先级的车道
* @param {*} lanes 车道
* @returns 车道
*/
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}
//console.log(getHighestPriorityLane(InputDiscreteLanes));//8 0b1000
//console.log('0000000000000000000000000011000');
//console.log('1111111111111111111111111101000');
/**
* 分离出所有比特位中最左边的1
* 分离出最低优先级的车道
* @param {*} lanes 车道
* @returns 车道
*/
function getLowestPriorityLane(lanes) {
const index = 31 - Math.clz32(lanes);
return index < 0 ? NoLanes : 1 << index;
}
console.log(getLowestPriorityLane(InputDiscreteLanes));//16 0b10000
console.log('0000000000000000000000000011000');
console.log(Math.clz32(InputDiscreteLanes));//27
console.log(31 - Math.clz32(InputDiscreteLanes));//4