React.js 性能优化技术
React.js 性能优化技术
本文将介绍一些优化 React.js 应用程序性能的方法。 React.js 帮助我们创建更快的 UI。但是,如果管理不当,它可能会降低应用程序的速度(例如,由于不必要的组件重新渲染)。
为了提高任何应用程序的性能,我们首先需要测量和识别应用程序中低于定义阈值的位置。然后,我们必须进一步调查和缓解这些区域并进行修复。
以下是我在职业生涯中用来衡量性能的一些资源,以及我用来优化我的 React 应用程序的技术。
React.js 中的性能测量技术
- 安装 反应开发者工具 为了 铬合金 , 火狐, 或者 React 的独立节点包 .该工具帮助我们收集有关不同组件渲染时间的信息,并帮助我们确定它们是否是实际的性能瓶颈。
** 参考:** __使用 React Profiler 的官方分步指南 - 在 Chrome DevTools 中,使用 审核选项卡 生成报告并分析各种指标和改进领域。
** 参考:** __DigitalOcean 的博客来衡量性能瓶颈 - 在 Chrome DevTools 中,使用 性能选项卡 记录、执行性能分析和跟踪有问题的地方。
** 参考:** __记录和查看性能跟踪 - 各种各样的 图书馆 还可以帮助追踪和确定需要改进的领域。几个例子——
[ 网络生命体征](https://www.npmjs.com/package/web-vitals)
,[ 反应插件性能](https://www.npmjs.com/package/react-addons-perf)
.
快速提示
1. 我们必须多次阅读,以确保结果是真实的,并且不受任何其他外部因素的影响。
2. 我们可以密切关注 Web 控制台以查看警告(在开发模式期间)。警告有时可能是有益的,可以帮助我们提高应用的整体质量。
3. 我们必须留意代价高昂的重新渲染。我们的代码中可能很少有地方会引发不必要的组件重新渲染。
React.js 中的性能优化技术
TLDR: 这些技术的简短版本最初由我在 我们是社区 .
1. 覆盖 shouldComponentUpdate 生命周期方法
反应组件在发生变化时呈现 道具
或者 状态
.覆盖 应该组件更新()
将帮助我们控制和避免任何不必要的重新渲染。
[ 应该组件更新()](https://reactjs.org/docs/react-component.html#shouldcomponentupdate)
在重新渲染组件之前触发。
我们将比较当前和下一个 道具
& 状态
.然后,返回 真的
如果我们想重新渲染;否则,返回 错误的
以避免重新渲染。
函数 shouldComponentUpdate(next_props, next_state) {
返回 next_props.id !== this.props.id;
}
在更高级别组件中触发的任何更新 (如下图中的 A1) 还将触发其子组件的更新,从而导致性能下降。
Nested Component Structure
因此,在更高级别的组件中添加检查并覆盖 应该组件更新()
方法可以在嵌套组件结构中发挥作用,并避免任何额外的重新渲染。
2. 使用 React.PureComponent
而不是覆盖 应该组件更新()
方法,我们可以简单地创建一个从[ React.PureComponent](https://reactjs.org/docs/react-api.html#reactpurecomponent)
.
类 ListOfBooks 扩展 React.PureComponent {
使成为() {
返回<div>{this.props.books.join(',')}</div> ;
}
}
**坏处?
** 它对当前和以前进行了浅显的比较 道具 & 状态 ,并在处理更复杂的数据结构(如嵌套对象)时产生错误。
例子:
类 ListOfBooks 扩展 React.Component {
构造函数(道具){
超级(道具);
这个.state = {
书籍:['富爸爸穷爸爸']
};
this.handleClick = this.handleClick.bind(this);
}
手柄点击(){
**//不推荐这种方式**
常量书籍 = this.state.books;
books.push('从优秀到优秀');
this.setState({books: books});
}
使成为() {
返回 (
<div>
<button onClick={this.handleClick} />
<ListOfBooks books={this.state.books} />
</div>
);
}
}
问题是 纯组件
将在旧值和新值之间进行简单比较 this.props.books
.
由于在 手柄点击()
方法,我们正在改变 图书
数组,旧值和新值 this.props.books
将比较相等,即使数组中的实际单词已更改。
**如何避免这种情况?
** 利用 不可变数据结构 连同使用 React.PureComponent
自动检查复杂的状态变化。
上述方法 手柄点击()
可以重写为以下任何一种 -
使用 **连接**
句法
手柄点击(){
this.setState(状态 => ({
书籍:state.books.concat(['思考并致富'])
}));
}
使用 **传播**
句法
手柄点击(){
this.setState(状态 => ({
书籍:[...state.books,“思考并致富”],
}));
};
同样,在 目的
,我们可以使用 对象.assign()
或者 传播
不改变对象的语法。
**\\不建议这样做 - 这会发生变异**
函数 updateBookAuthorMap(bookAuthorMap) {
bookAuthorMap.goodtogreat = '詹姆斯';
} **\\推荐的方式 - 没有变异**
函数 updateBookAuthorMap(bookAuthorMap) {
返回 Object.assign({}, bookAuthorMap, {goodtogreat: 'James'});
} **\\recommended way - without mutating - object spread syntax**
函数 updateBookAuthorMap(bookAuthorMap) {
return {...bookAuthorMap, goodtogreat: 'James'};
}
小建议
在处理深度嵌套的对象时,以不可变的方式更新它们可能非常具有挑战性。
对于这种情况,很少有库可以让我们编写高度可读的代码而不会失去其不变性的好处,比如——
[ 总是](https://github.com/immerjs/immer)
,[ 不变性助手](https://github.com/kolodny/immutability-helper)
,[ 不可变的.js](https://immutable-js.com/)
,[ 无缝不可变](https://www.npmjs.com/package/seamless-immutable)
,[ rect-copy-write](https://www.npmjs.com/package/react-copy-write)
.
3. 使用 React 片段
反应片段
帮助我们组织子组件列表,而无需在 DOM 中添加额外的节点。
在下图中,我们可以看到我们使用时节点数之间的明显差异 反应片段
与我们不这样做的时候相比。
Left Side: Use of Fragments | Right Side: Without the use of fragments
**//样本**
导出默认函数 App() {
返回 (
** <React.Fragment>**
<h1>你好组件应用程序</h1>
<h2>这是一个示例组件</h2>
** </React.Fragment>**
);
} **//或者,我们也可以使用 <></>表示片段**
导出默认函数 App() {
返回 (
** <>**
<h1>你好组件应用程序</h1>
<h2>这是一个示例组件</h2>
** </>**
);
}
你可以分叉这个 代码沙箱 为自己测试。
4. 节流和去抖动事件动作
- 识别我们代码中昂贵或多次执行的事件处理程序(例如,滚动、鼠标悬停、DOM 操作、处理大型列表等)
- 在这样的场景下, 节流 和 去抖 将成为救生员,而无需对事件处理程序进行重大更改。
- 节流—— 在指定时间过去后执行任何函数并帮助限制对函数的调用。
- 去抖—— 防止过于频繁地触发任何事件,即它不会调用该函数,直到在其上一次调用之后经过定义的持续时间。
我们可以使用 罗达什
库及其辅助函数 — 风门
和 去抖
.
例如 - 参考我的 **** 代码沙盒示例
5. 记忆 React 组件
我们可以使用 memoize 技术来存储任何昂贵的函数调用的结果并返回缓存的结果。
每当发生相同的执行时,这种技术将帮助我们优化函数的速度(即,如果使用与前一个相同的值调用函数,则它不会执行逻辑,而是返回缓存的结果)。
我们可以使用以下方式在 ReactJs 中进行记忆 -
5.1 矩形备忘录 **
**React.Memo 将记住组件一次,并且不会在下一次执行时渲染它,只要 道具 保持不变。
常量 BookDetails = ({book_details}) =>{
常量 {book_title, author_name, book_cover} = book_details;
返回 (
<div>
<img src={book_cover} />
<h4>{书名}</h4>
<p>{作者名}</p>
</div>
)
} **//记忆组件**
导出 const MemoizedBookDetails = React.memo(BookDetails)
**//React 将在第一次渲染时调用 MemoizedBookDetails**
<MemoizedBookDetails
book_title="富爸爸穷爸爸"
author_name="罗伯特"
/> **//React 不会在下次渲染时调用 MemoizedBookDetails**
<MemoizedBookDetails
book_title="富爸爸穷爸爸"
author_name="罗伯特"
/>
5.2 反应钩子 使用备忘录 **
**它有助于避免在组件中重新执行相同的昂贵功能。当我们传递一个 支柱 在数组或对象的子组件中,然后 使用备忘录 将记住渲染之间的值。
例子 -
从“反应”导入 { useState, useMemo };
导出函数CalculateBookPrice() {
const [价格,setPrice] = useState(1);
const [增量,setIncrement] = useState(0);
**const newPrice = useMemo(() = > finalPrice(number), [number]);**
常量 onChange = 事件 => {
setPrice(Number(event.target.value));
};
const onClick = () => setIncrement(i => i + 1);
返回 (
<div>
新书价
<input type="number" value={price} onChange={onChange} />
是 {newPrice}
<button onClick={onClick}>重新渲染</button>
</div>
);
}
函数最终价格(n){
返回 n <= 0 ? 1 : n * finalPrice(n * 0.25);
}
5.3 莫泽 **用于记忆任何纯方法的库
** 这是一个用于 JavaScript 的记忆库。
例子 -
从“moize”导入moize; 常量 BookDetails = ({book_details}) =>{
常量 {book_title, author_name, book_cover} = book_details; 返回 (
<div>
<img src={book_cover} />
<h4>{书名}</h4>
<p>{作者名}</p>
</div>
)
} **导出默认moize(BookDetails,{
反应:真
});**
6. 使用 React Hook useCallback
-
在 React 中,当一个组件重新渲染时,每个方法都会重新生成。
-
useCallback(函数,依赖项)
可以帮助我们返回一个随 依赖关系 (即,不是在每次渲染中重新创建函数的实例,而是使用相同的实例) -
例子 - 一个很好的用例是当我们想要呈现一个广泛的项目列表时。
从“反应”导入 { useCallback }; 导出函数 MyBook({ book }) { 常量 onItemClick = useCallback(event = > {
console.log('你点击了', event.currentTarget);
}, [书]); 返回 (
<MyBookList
书={书}
onItemClick={onItemClick}
/>
);
}
小建议 - 我们需要确保我们使用 React Hook
_使用回调_
仅适用于相关案例,不要在多个地方过度使用它。
参考: 不要过度使用 React UseCallback
7. 使用 Web Worker 处理 CPU 广泛的任务
-
网络工作者 在中运行脚本 背景 , 与 主执行线程 .
-
这个后台线程将帮助主线程(UI)运行而不会被阻塞或没有任何延迟。
-
由于 JavaScript 是单线程的,我们需要使用以下任一方法并行计算昂贵的操作 -
一个。 伪并行(使用设置超时
)
B. 网络工作者 -
下面是一个使用 Web Workers 的例子
//零件
导出默认书籍扩展 React.Component{ 构造函数(道具){
超级(书籍);
} 状态 = {
书籍:this.props.books
} 组件DidMount() {
this.worker = new Worker('booksorter.worker.js');this.worker.addEventListener('message', event => {
const sortedBooks = event.data;
这个.setState({
书籍:sortedBooks
})
});
} doSortingByReaders = () => {
如果(this.state.books && this.state.books.length){
this.worker.postBookDetails(this.state.books);
}
} 使成为(){
常量书籍 = this.state.books;
返回 (
<>
</>
)
}
} // booksorter.worker.js
导出默认函数 sort() {
self.addEventListener('消息', e =>{
如果(!e)返回;
让书籍= e.data;//排序逻辑
postBookDetails(书籍);
});
}
在上面的代码中,我们在一个单独的线程中执行了 sort 方法。这确保我们不会阻塞主线程。
Web Worker 的用例 — 图像处理、排序、过滤或任何广泛的 CPU 任务。
官方参考: __使用网络工作者
8. 使用动态 import() 进行代码拆分
-
当一个反应应用程序在浏览器中呈现时,然后 捆绑 包含应用程序整个代码的文件加载并提供给用户。
-
捆绑 很有帮助,因为它减少了页面可以处理的服务器请求的数量。
-
反应文件的大小随着应用程序大小的增加而增加。因此,增加了捆绑包的大小。这种增加可能会减慢页面的初始加载速度。
-
将一个大包文件拆分为多个 块 , 我们可以用 动态的
**进口()**
**** 随着 延迟加载技术 使用React.lazy
.//普通方式
从“./components/Book”导入图书;
从“./components/BookDetails”导入 BookDetails; //反应懒惰方式
const Book = React.lazy(() => import("./components/Book")); const BookDetails = React.lazy(() => import("./components/BookDetails")); import("./Book").then(book => {
...//逻辑
});
惰性组件必须在内部渲染[ 悬念](https://reactjs.org/docs/code-splitting.html#reactlazy:~:text=The%20lazy%20component%20should%20then%20be%20rendered%20inside%20a%20Suspense%20component%2C%20which%20allows%20us%20to%20show%20some%20fallback%20content%20\(such%20as%20a%20loading%20indicator\)%20while%20we%E2%80%99re%20waiting%20for%20the%20lazy%20component%20to%20load.)
零件。这 悬念
将允许我们在 React 等待在前端渲染组件时显示加载文本或任何指示符作为后备。
<React.Suspense fallback={<p>正在加载页面...</p> }>
<Route path="/Book" exact>
<Book/>
</Route>
<Route path="/BookDetails">
<BookDetails/>
</Route>
</React.Suspense>
9. 虚拟化/窗口化长列表数据
- 要渲染一个广泛的数据列表,我们不能一次渲染整个列表。相反,在可见视口内一次只渲染列表的一小部分。
- 当用户滚动时渲染更多数据,为了实现这一点,我们可以使用各种库,如
[ 反应窗口](https://www.npmjs.com/package/react-window)
,[ 反应虚拟化](https://www.npmjs.com/package/react-virtualized)
, ETC。
10. React v 18 的新改进(2022 年 3 月发布)
React 18 于今年发布,通过新更新的渲染引擎和更多功能来提高应用程序性能。
参考: __React 18 新特性
结论
就这样。如果您喜欢这篇文章,或者您是否知道更多优化 React.js 应用程序性能的方法,请告诉我。
上面的大部分例子都是我的实践经验,我希望这些对你有用。
参考:
升级编码
感谢您成为我们社区的一员!你走之前:
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明