React 17发布在即,我提前用RC版本做了一些项目升级和实验。本文总结React 17的新特性和最佳实践,包括新的JSX转换、事件系统改进、生命周期变更、并发模式准备、升级注意事项等方面的经验。这些经验是我在实际项目中踩坑踩出来的,希望能帮你顺利升级到React 17,充分利用新特性提升开发效率和应用性能。
一、React 17概述
React 17是React的一个重要版本,但和之前的版本不同,React 17没有引入太多面向开发者的新API。它的主要变化是底层的改进,为未来的并发模式(Concurrent Mode)打下基础。
React团队把React 17称为"垫脚石"版本(stepping stone release)。它的主要目标是:
- 让React的升级更容易,支持渐进式升级
- 改进事件系统,解决一些长期存在的问题
- 引入新的JSX转换,让JSX不再依赖React全局变量
- 为并发模式做准备,优化底层架构
虽然React 17没有太多新API,但它的底层改进对应用的性能和可维护性有很大影响。而且,React 17是未来并发模式的基础,升级到React 17是为未来做准备。
我在几个项目中升级到了React 17 RC版本,踩了一些坑,也积累了一些经验。下面分享给大家。
二、新的JSX转换
新的JSX转换是React 17最面向开发者的新特性,也是最实用的新特性。
1. 什么是新的JSX转换
在React 17之前,JSX会被编译成React.createElement调用。比如:
const element = <h1>Hello World</h1>;会被编译成:
const element = React.createElement('h1', null, 'Hello World');这意味着,在使用JSX的文件中,必须导入React,否则会报错。这也是为什么每个React组件文件开头都要写import React from 'react'。
React 17引入了新的JSX转换,JSX不再被编译成React.createElement,而是被编译成从react/jsx-runtime导入的jsx函数:
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { children: 'Hello World' });这样,使用JSX的文件就不需要导入React了。
2. 新JSX转换的好处
新的JSX转换有几个好处:
- 不需要导入React:使用JSX的文件不再需要
import React from 'react',减少了样板代码 - 更好的性能优化:新的jsx函数比React.createElement更轻量,编译器可以做更多优化
- 更好的错误提示:新的转换方式让错误提示更准确
- 为未来做准备:新的转换方式是React未来的方向,提前适应有好处
3. 如何启用新的JSX转换
要启用新的JSX转换,需要升级构建工具的配置:
- Babel:升级@babel/preset-react到7.9.0以上,配置
{"runtime": "automatic"} - TypeScript:升级TypeScript到4.1以上,配置
"jsx": "react-jsx" - Create React App:升级react-scripts到4.0以上,自动支持
- Next.js:升级Next.js到9.5以上,自动支持
Babel配置示例:
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}TypeScript配置示例:
{
"compilerOptions": {
"jsx": "react-jsx"
}
}4. 最佳实践
启用新的JSX转换之后,有几点需要注意:
- 移除不必要的React导入:启用新转换后,很多文件中的
import React from 'react'就不需要了。可以用ESLint的react/jsx-uses-react规则来检测和移除不必要的导入 - 保留需要的React导入:如果文件中使用了React的其他API(比如useState、useEffect、React.memo等),还是需要导入。可以只导入需要的API,比如
import { useState, useEffect } from 'react' - 第三方库兼容性:大部分第三方库都兼容新的JSX转换,但有些老的库可能还有问题。升级后要测试一下
- 渐进式迁移:可以先在新文件中使用新转换,老文件保持不变。新转换和旧转换可以共存,不需要一次性全部迁移
我在项目中启用新JSX转换后,移除了几百个不必要的React导入,代码更简洁了。而且,新的转换方式确实让构建速度有一点提升,虽然不明显。
三、事件系统改进
React 17对事件系统做了重要改进,解决了一些长期存在的问题。
1. 事件委托的变化
在React 17之前,React会把所有事件都委托到document上。也就是说,不管你在哪个元素上绑定事件,最终都会在document上处理。
这种方式有一些问题:
- 如果页面上有多个React应用,事件会在document上冲突
- 如果在document上停止了事件冒泡,会影响React的事件处理
- 和其他库(比如jQuery)的事件处理可能冲突
React 17改进了事件委托,不再把事件委托到document,而是委托到React应用的根容器(root container)上。也就是调用ReactDOM.render()的那个DOM元素。
这样,每个React应用的事件都在自己的根容器上处理,不会互相干扰。多个React应用可以共存,也更容易和其他库集成。
2. onScroll事件不再冒泡
在React 17之前,onScroll事件会冒泡,这经常导致问题。比如,父元素的onScroll会被子元素的滚动触发,导致不必要的事件处理。
React 17修复了这个问题,onScroll事件不再冒泡。这和浏览器的原生行为一致,也减少了意外的事件触发。
如果你的代码中依赖了onScroll的冒泡行为,升级后需要注意,可能需要调整代码。
3. onFocus和onBlur的变化
React 17中,onFocus和onBlur使用了原生的focusin和focusout事件,而不是focus和blur事件。
这带来了几个好处:
- onFocus和onBlur现在可以正确地冒泡了(之前的实现有问题)
- 事件对象的表现更一致
- 和浏览器原生行为更接近
如果你的代码中使用了onFocus/onBlur的冒泡,升级后行为会更正确,但也要注意测试。
4. 事件池的移除
在React 17之前,React使用事件池(event pooling)来复用事件对象,减少垃圾回收。这意味着,事件对象在事件处理函数执行完之后就会被重置,不能异步使用。
比如,下面的代码在React 16中会有问题:
function handleClick(e) {
setTimeout(() => {
console.log(e.target); // e已经被重置了,target是null
}, 100);
}需要调用e.persist()来保留事件对象:
function handleClick(e) {
e.persist();
setTimeout(() => {
console.log(e.target); // 正常
}, 100);
}React 17移除了事件池,事件对象不再被重置,可以异步使用。不再需要调用e.persist()了。
这是一个很实用的改进,减少了很多因为事件对象被重置而导致的bug。
5. 最佳实践
- 测试事件相关的代码:升级后,重点测试事件相关的功能,尤其是onScroll、onFocus、onBlur的行为
- 移除不必要的e.persist():React 17不再需要e.persist(),可以移除。但保留也不会有问题,只是多余的
- 利用根容器事件委托:如果页面上有多个React应用,React 17的事件委托改进让它们可以更好地共存
- 注意和第三方库的事件冲突:虽然React 17改进了事件系统,但和第三方库的事件冲突还是可能存在,要注意测试
四、生命周期和其他API变更
React 17对一些生命周期和API做了变更。
1. 废弃生命周期的警告
React 16.3就废弃了几个生命周期:componentWillMount、componentWillReceiveProps、componentWillUpdate。在React 17中,使用这些生命周期会有更强烈的警告。
这些废弃的生命周期应该替换为:
- componentWillMount → constructor + componentDidMount
- componentWillReceiveProps → getDerivedStateFromProps + componentDidUpdate
- componentWillUpdate → getSnapshotBeforeUpdate + componentDidUpdate
如果你的项目中还在使用这些废弃的生命周期,建议尽快迁移。React 18可能会彻底移除这些生命周期。
2. getDerivedStateFromError的改进
React 17改进了getDerivedStateFromError错误边界(Error Boundary)的行为。在React 17之前,getDerivedStateFromError在渲染阶段调用,不应该有副作用。但很多人在里面写了副作用,导致问题。
React 17中,getDerivedStateFromError的行为更严格,应该只用来更新state,不应该有副作用。副作用应该放在componentDidCatch中。
如果你的错误边界中在getDerivedStateFromError里写了副作用,升级后需要调整。
3. forwardRef的改进
React 17改进了forwardRef的显示名称。在React DevTools中,forwardRef组件的显示名称现在更准确了。
之前,forwardRef组件在DevTools中显示为"ForwardRef",不容易区分。现在,会显示组件的实际名称,调试更方便。
4. 原生组件的属性支持
React 17增加了对一些原生DOM属性的支持:
- 支持
onAuxClick和onAuxClickCapture(中键点击) - 支持
onToggle事件(details元素的展开/收起) - 支持
onContextMenu的捕获版本 - 支持
disablePictureInPicture属性(video元素) - 支持
controlsList属性(video元素)
这些改进让React可以更好地支持原生DOM的新特性。
5. 最佳实践
- 迁移废弃的生命周期:尽快把componentWillMount等废弃生命周期迁移到新的API
- 正确使用错误边界:getDerivedStateFromError只更新state,副作用放在componentDidCatch
- 利用新的原生属性支持:需要用到新的DOM属性时,直接用,不需要再用ref或dangerouslySetInnerHTML
- 关注React DevTools的改进:升级React DevTools到最新版本,获得更好的调试体验
五、并发模式准备
React 17最重要的底层改进,是为并发模式(Concurrent Mode)做准备。
1. 什么是并发模式
并发模式是React的一个革命性特性,它让React可以中断渲染,优先处理更重要的更新。这样,即使在渲染大量内容的时候,用户交互也能保持流畅。
并发模式的核心概念:
- 可中断渲染:React可以在渲染过程中暂停,处理更高优先级的任务,然后再回来继续渲染
- 优先级调度:不同的更新有不同的优先级,高优先级的更新先处理
- Suspense:组件可以"等待"异步数据,在等待期间显示fallback
- useTransition:标记非紧急更新,让UI保持响应
并发模式在React 17中还是实验性的,需要手动开启。React 18会正式发布并发模式。
2. React 17为并发模式做了哪些准备
React 17虽然没有正式发布并发模式,但做了很多底层改进:
- 事件系统重构:新的事件委托方式,让事件处理可以更好地和并发模式配合
- 渲染架构优化:优化了Fiber架构,为可中断渲染打下基础
- 自动批处理改进:React 17改进了自动批处理(automatic batching),更多的更新会被批量处理,减少渲染次数
- Suspense改进:改进了Suspense的行为,为未来的并发模式下的Suspense做准备
3. 如何为并发模式做准备
虽然并发模式还没正式发布,但现在就可以开始为它做准备:
- 保持组件的纯粹性:并发模式下,渲染可能会被中断和重新执行,所以渲染函数应该是纯粹的,没有副作用。不要在render中修改外部变量、调用API等
- 正确使用useEffect:副作用放在useEffect中,不要放在渲染过程中
- 避免在渲染中使用ref:ref在并发模式下可能会有问题,尽量在useEffect中使用ref
- 测试严格模式:开启严格模式(StrictMode),它会检测不安全的生命周期和副作用,帮助你发现并发模式下可能有问题的代码
- 关注useTransition和useDeferredValue:了解这些新的Hook,为未来使用并发模式做准备
4. 最佳实践
- 现在就开启严格模式:严格模式能帮你发现很多潜在问题,是为并发模式做准备的最好方式
- 重构有副作用的渲染函数:检查你的组件,确保渲染函数没有副作用
- 不要用UNSAFE_前缀的生命周期:这些生命周期在并发模式下会有问题,尽快迁移
- 关注React团队的更新:并发模式还在快速发展,关注React团队的博客和更新,及时了解最新进展
我在项目中开启了严格模式,发现了几个有副作用的渲染函数,重构之后,代码更健壮了,也为未来的并发模式做好了准备。
六、升级注意事项
升级到React 17,有一些需要注意的地方。
1. 渐进式升级
React 17支持渐进式升级。也就是说,你可以在一个应用中同时使用React 16和React 17,逐步迁移。
渐进式升级的方式:
- 用React 17的新根API(ReactDOM.createRoot)创建新的React 17应用
- 老的部分继续用React 16的ReactDOM.render
- 两部分可以通过DOM事件或全局状态通信
渐进式升级适合大型项目,可以降低升级风险。但对于中小型项目,建议一次性升级,更简单。
2. 依赖兼容性
升级前,要检查项目依赖的第三方库是否兼容React 17。
大部分主流库(比如Redux、React Router、Ant Design、Material UI)都已经兼容React 17。但一些比较老的、维护不活跃的库可能还有问题。
升级前,可以:
- 查看库的GitHub Issues,看有没有人报告React 17的兼容性问题
- 先在分支上升级,跑测试,看看有没有问题
- 如果有兼容性问题,可以考虑升级库的版本,或者替换库
3. 测试
升级后,要充分测试。重点测试:
- 事件相关的功能(onScroll、onFocus、onBlur等)
- 错误边界的行为
- 表单输入和受控组件
- 第三方组件的行为
- 性能(React 17应该不会变慢,但要确认)
建议有自动化测试的项目,先跑一遍测试,再手动测试关键功能。
4. 构建工具升级
升级React 17,可能需要升级构建工具:
- Babel:升级@babel/preset-react到7.9.0以上
- TypeScript:升级到4.1以上(如果要用新的JSX转换)
- ESLint:升级eslint-plugin-react到最新版本
- Webpack/Vite:一般不需要特别升级,但要确认兼容
5. 最佳实践
- 先在开发环境升级测试:不要直接在生产环境升级,先在开发环境测试,确认没问题再发布
- 保留回滚方案:升级前做好备份,保留回滚的方案,万一有问题可以快速回滚
- 逐步启用新特性:不要一次性启用所有新特性,先启用最稳定的(比如新的JSX转换),再逐步启用其他
- 关注React 17的更新日志:仔细阅读React 17的更新日志,了解所有的变更和注意事项
七、性能优化建议
升级到React 17后,可以结合新特性做一些性能优化。
1. 利用自动批处理
React 17改进了自动批处理。在React 16中,只有React事件处理函数中的更新会被批量处理。在React 17中,Promise、setTimeout、原生事件处理函数中的更新也会被批量处理。
这意味着,你不需要再手动用unstable_batchedUpdates来批量处理更新了。React会自动帮你批量处理,减少渲染次数。
如果你的代码中有手动调用unstable_batchedUpdates的地方,在React 17中可以移除了(大部分情况下)。
2. 合理使用React.memo
React.memo可以避免不必要的重新渲染。在React 17中,配合新的JSX转换和自动批处理,React.memo的效果更好。
建议:
- 对渲染开销大的组件使用React.memo
- 确保传给组件的props是稳定的(用useMemo、useCallback)
- 不要滥用React.memo,对于简单的组件,memo的开销可能大于收益
3. 代码分割
React 17和React.lazy、Suspense配合,可以更好地做代码分割。
建议:
- 用React.lazy做路由级别的代码分割
- 对于大的组件,也可以用React.lazy做组件级别的代码分割
- 配合Suspense的fallback,提供更好的加载体验
4. 利用新的DevTools分析性能
React 17配合最新的React DevTools,可以更好地分析性能。新版DevTools提供了:
- 组件渲染时间的分析
- 渲染原因的追踪
- 提交(commit)的详细信息
- 交互追踪(Interaction Tracking)
用DevTools分析应用的性能,找到渲染瓶颈,针对性优化。
八、写在最后
React 17虽然没有太多面向开发者的新API,但它是一个重要的版本。它的底层改进,为未来的并发模式打下了基础,也解决了一些长期存在的问题。
新的JSX转换,让代码更简洁,也让构建更高效。事件系统的改进,让React的事件处理更正确,也更容易和其他库集成。并发模式的准备,让我们可以提前为未来做准备。
升级到React 17,总体来说是比较平滑的。大部分项目升级后不需要做太多改动,就能享受React 17的改进。但也要注意一些breaking change,尤其是事件系统的变化,要充分测试。
我在几个项目中升级到React 17之后,整体体验不错。新的JSX转换让代码更干净,事件系统的改进解决了一些老问题,严格模式帮我发现了一些潜在问题。虽然并发模式还没正式发布,但为它做准备的过程,也让我的代码更健壮了。
React的发展很快,从Hooks到并发模式,React一直在进化。作为开发者,我们要保持学习,跟上React的发展,充分利用新特性,写出更好的代码。
最后,用一句话结束本文:"React 17是垫脚石,也是新起点。升级它,不仅是为了现在的改进,更是为了未来的可能。"愿每一个React开发者,都能顺利升级到React 17,在React的世界里写出更优雅、更高效的代码。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录