React 应用性能优化指南
在开发 React 应用时,性能优化是一个至关重要的环节。本文将介绍多种优化策略,包括服务器端渲染、摇树优化、按需更新 DOM、使用 CSV 代替 JSON 以及预渲染、预取和预缓存等,帮助你提升应用的性能和用户体验。
服务器端渲染(SSR)
创建 React 应用(CRA)单页应用(SPA)模式在某些情况下表现出色,因为它不会进行页面刷新,让用户感觉像是在使用移动应用。然而,CRA 并不直接支持服务器端渲染(SSR)。虽然可以通过配置路由等方式让 CRA 实现 SSR,但这可能需要自行维护配置,且不一定值得。
如果你需要 SSR 来提升性能,建议使用已经配置好 SSR 的 React 库,如 Next.js 框架、Razzle 或 Gatsby。
摇树优化(Tree Shaking)
摇树优化是 JavaScript 中用于移除死代码的技术。死代码包括两种情况:
- 从未执行的代码:在运行时从未被执行的代码。
- 结果未被使用的代码:代码被执行,但结果从未被使用。
在当前项目配置中,如果使用了 Recoil 状态管理但未实际使用其功能,会增加代码体积。例如,在
src/AppRouter.tsx
中移除 Recoil 引用可以优化代码:
// 优化前
import React, { FunctionComponent, Suspense } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { RecoilRoot } from 'recoil'
import App from './App'
const AppRouter: FunctionComponent = () => {
return (
<Router>
<RecoilRoot>
<Suspense fallback={<span>Loading...</span>}>
<Switch>
<Route exact path="/" component={App} />
</Switch>
</Suspense>
</RecoilRoot>
</Router>
)
}
// 优化后
import React, { FunctionComponent } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import App from './App'
const AppRouter: FunctionComponent = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={App} />
</Switch>
</Router>
)
}
还可以通过
yarn remove recoil
命令从
package.json
文件中移除 Recoil。移除未使用的库导入后,运行
yarn analyzer
可以看到代码体积减小。
按需更新 DOM
在使用 D3 和 React 时,让 React 控制 DOM 是一个很大的优势。但需要确保仅在数据发生变化时重新渲染。D3 代码被视为副作用,因为它在 React 的虚拟 DOM 机制之外向 DOM 添加内容。
以
HelloD3Data.tsx
组件为例,每次组件更新时都会重新绘制。为了避免不必要的重绘,可以采用以下三种方法:
-
检查 D3 数据
:通过选择所有
p
元素并迭代数组,创建一个先前数据对象进行比较。
import React, { useEffect } from 'react'
import './HelloD3Data.scss'
import { select, selectAll } from 'd3-selection'
interface IHelloD3DataProps {
data: string[]
}
const HelloD3Data = (props: IHelloD3DataProps) => {
useEffect(() => {
draw()
})
const draw = () => {
const previousData: string[] = []
const p = selectAll('p')
p.each((d, i) => {
previousData.push(d as string)
})
if ( JSON.stringify(props.data) !== JSON.stringify(previousData) ) {
console.log('draw!')
select('.HelloD3Data')
.selectAll('p')
.data(props.data)
.enter()
.append('p')
.text((d) => `d3 ${d}`)
}
}
return (
<div className="HelloD3Data">
</div>
)
}
interface IHelloD3DataProps {
data: string[]
}
export default HelloD3Data
- 克隆数据 :克隆数据并将 props 值与状态值进行比较。
import React, { RefObject, useEffect, useState } from 'react'
import './HelloD3Data.scss'
import { select } from 'd3-selection'
const ref: RefObject<HTMLDivElement> = React.createRef()
const HelloD3DataCloned = (props: IHelloD3DataProps) => {
const [data, setData] = useState<string[]>([])
useEffect(() => {
if (JSON.stringify(props.data) !== JSON.stringify(data)){
setData(props.data)
console.log('draw!')
select(ref.current)
.selectAll('p')
.data(data)
.enter()
.append('p')
.text((d) => `d3 ${d}`)
}
}, [data, props.data, setData])
return <div className="HelloD3Data" ref={ref} />
}
interface IHelloD3DataProps {
data: string[]
}
export default HelloD3DataCloned
-
创建 React 类组件
:使用类组件的生命周期钩子(如
componentDidUpdate)来比较先前和当前的 props 数据。
import React, { RefObject } from 'react'
import { select } from 'd3-selection'
export default class HelloD3DataClass extends React.PureComponent<IHelloD3DataClassProps, IHelloD3DataClassState> {
ref: RefObject<HTMLDivElement>
constructor(props: IHelloD3DataClassProps) {
super(props)
this.ref = React.createRef()
}
componentDidMount() {
this.draw()
}
componentDidUpdate(prevProps: IHelloD3DataClassProps, prevState: IHelloD3DataClassState) {
if (JSON.stringify(prevProps.data) !== JSON.stringify(this.props.data)) {
this.draw()
}
}
draw = () => {
console.log('draw!')
select(this.ref.current)
.selectAll('p')
.data(this.props.data)
.enter()
.append('p')
.text((d) => `d3 ${d}`)
}
render() {
return (
<div className="HelloD3DataClass" ref={this.ref} />
)
}
}
interface IHelloD3DataClassProps {
data: string[]
}
interface IHelloD3DataClassState {
// TODO
}
使用 CSV 代替 JSON
在创建 D3 图表时,CSV 和 JSON 是常用的数据格式。如果有选择的话,建议使用 CSV,因为它具有以下优点:
-
带宽占用少
:CSV 使用字符分隔符,而 JSON 需要更多字符来表示语法格式。
-
数据处理速度快
:CSV 的字符分隔符更容易拆分,而 JSON 需要解释语法。
优化 CRA 应用
可以通过预渲染、预取和预缓存来优化 CRA 应用。
预渲染
CRA 本身不支持服务器端渲染,但可以进行预渲染。推荐使用 React-snap 进行预渲染,它可以自动创建不同路由的预渲染 HTML 文件。
操作步骤如下:
1. 安装 React-snap:
$ yarn add --dev react-snap
-
在
package.json中添加 post build 脚本:
"scripts": {
...
"postbuild": "react-snap"
}
-
为了避免“无样式内容闪烁”(FOUC)问题,可以在
package.json中启用内联 CSS:
"reactSnap": {
"inlineCss": true
}
-
在
src/index.tsx中进行水合操作并注册预缓存:
import React from 'react'
import { hydrate, render } from 'react-dom'
import './index.scss'
import AppRouter from './AppRouter'
import * as serviceWorker from './serviceWorker'
const rootElement = document.getElementById('root')
if (rootElement && rootElement!.hasChildNodes()) {
hydrate(<AppRouter />, rootElement)
serviceWorker.register()
} else {
render(<AppRouter />, rootElement)
}
- 构建生产版本的应用:
$ yarn build
添加新页面
在
AppRouter.tsx
中添加新页面路由:
import Rectangle from './components/Rectangle/Rectangle'
const AppRouter: FunctionComponent = () => {
return (
<Router>
<RecoilRoot>
<Suspense fallback={<span>Loading...</span>}>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/Rectangle" component={Rectangle} />
</Switch>
</Suspense>
</RecoilRoot>
</Router>
)
}
为了让新页面被爬取,需要在
App.tsx
中添加路由链接:
<NavLink to='/Rectangle' key='Rectangle'>
Navigate To Rectangle
</NavLink>
预取(Prefetching)
可以使用 Quicklink 来优化 JS 包的加载顺序,让页面先加载,然后再获取 JS 包。
操作步骤如下:
1. 安装依赖:
$ yarn add -D webpack-route-manifest
$ yarn add quicklink
-
在
src/AppRouter.tsx中使用 Quicklink HOC 添加预取功能:
import React, { FunctionComponent, lazy, Suspense } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { RecoilRoot } from 'recoil'
import { withQuicklink } from 'quicklink/dist/react/hoc.js'
import App from './App'
const MyPage = lazy(() => import('./components/Rectangle/Rectangle'))
const options = {
origins: [],
}
const AppRouter: FunctionComponent = () => {
return (
<Router>
<RecoilRoot>
<Suspense fallback={<span>Loading...</span>}>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/Rectangle" component={withQuicklink(MyPage, options)} />
</Switch>
</Suspense>
</RecoilRoot>
</Router>
)
}
export default AppRouter
通过以上优化策略,你可以显著提升 React 应用的性能和用户体验。但需要注意的是,预渲染和静态页面服务并不总是最佳选择,需要根据应用的具体情况进行测试和调整。
React 应用性能优化指南
性能优化策略总结
为了更清晰地展示上述各种性能优化策略,下面通过表格进行总结:
| 优化策略 | 具体方法 | 优点 | 操作步骤 |
| — | — | — | — |
| 服务器端渲染(SSR) | 使用 Next.js 框架、Razzle 或 Gatsby | 提升性能 | 选择合适的框架并进行相应配置 |
| 摇树优化(Tree Shaking) | 移除未使用的库和代码 | 减小代码体积 | 移除代码中的引用,使用
yarn remove
移除库,运行
yarn analyzer
检查 |
| 按需更新 DOM | 检查 D3 数据、克隆数据、创建 React 类组件 | 避免不必要的重绘 | 按对应代码示例修改组件 |
| 使用 CSV 代替 JSON | 在创建 D3 图表时优先选择 CSV | 减少带宽占用,加快数据处理速度 | 选择 CSV 作为数据格式 |
| 预渲染 | 使用 React-snap | 自动创建预渲染 HTML 文件 | 安装 React-snap,配置
package.json
,在
src/index.tsx
处理 |
| 预取(Prefetching) | 使用 Quicklink | 优化 JS 包加载顺序 | 安装依赖,在
src/AppRouter.tsx
中使用 Quicklink HOC |
各优化策略的流程图
下面是预渲染和预取优化策略的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([开始]):::startend --> B(安装 React-snap):::process
B --> C(配置 package.json 脚本):::process
C --> D(启用内联 CSS):::process
D --> E(在 src/index.tsx 处理):::process
E --> F(构建生产版本):::process
F --> G([结束]):::startend
H([开始]):::startend --> I(安装依赖):::process
I --> J(在 src/AppRouter.tsx 使用 Quicklink HOC):::process
J --> K([结束]):::startend
不同优化策略的应用场景分析
- 服务器端渲染(SSR) :适用于对搜索引擎优化(SEO)要求较高、需要快速加载首屏内容的应用。例如新闻网站、电商平台等,通过 SSR 可以让搜索引擎更好地抓取页面内容,同时提升用户首次访问的加载速度。
- 摇树优化(Tree Shaking) :在项目中引入了大量第三方库,但实际使用的功能较少时,摇树优化可以有效减小代码体积。比如在一个小型的 React 工具应用中,引入了一个功能丰富的 UI 库,但只使用了其中的几个组件,此时摇树优化就可以移除未使用的代码。
- 按需更新 DOM :在使用 D3 进行数据可视化的 React 应用中,由于 D3 操作 DOM 可能会导致不必要的重绘,按需更新 DOM 可以避免这种情况。例如实时数据监控图表,数据更新频率较高,通过按需更新可以提高性能。
- 使用 CSV 代替 JSON :当处理大量数据时,CSV 可以减少带宽占用和提高数据处理速度。比如在大数据分析平台中,从服务器获取大量数据进行图表展示,使用 CSV 可以提升性能。
- 预渲染 :对于单页应用(SPA),预渲染可以在一定程度上模拟服务器端渲染的效果,提升页面加载速度和 SEO。例如企业官网、博客网站等,通过预渲染可以让搜索引擎更好地抓取页面,同时用户可以更快地看到页面内容。
- 预取(Prefetching) :适用于页面中包含大量 JS 资源的应用,通过预取可以提前加载后续页面所需的资源,提升用户导航到后续页面的速度。比如大型电商网站的商品列表页和详情页,用户在浏览列表页时可以预取详情页的资源。
注意事项和总结
在进行性能优化时,需要注意以下几点:
- 预渲染和静态页面服务并不总是最佳选择,对于轻量级应用,可能等待所有内容一次性加载完成会有更好的用户体验。需要根据应用的具体情况进行测试和调整。
- 在使用 React-snap 进行预渲染时,可能会出现“无样式内容闪烁”(FOUC)问题,需要通过启用内联 CSS 来解决。
- 在移除未使用的库和代码时,要确保不会影响应用的正常功能,建议在移除前进行充分的测试。
总之,通过合理运用上述性能优化策略,可以显著提升 React 应用的性能和用户体验。在实际开发中,需要根据应用的特点和需求,选择合适的优化策略,并进行充分的测试和调整。
超级会员免费看
2454

被折叠的 条评论
为什么被折叠?



