您的 React 应用程序是否感到有点缓慢? 您是否因为您可能看到的内容而害怕在 Chrome DevTools 中打开油漆闪电
? 尝试这些 5 个性能提示!
本文包含 React 开发的 5 个性能提示. 您可以使用此内容表快速浏览本文。
跳跃到Tips
使用备忘录和PureComponent
您是否认为<ComponentB>
如果只有props.propA
更改值,会重新渲染?
1import React from 'react';
2
3const MyApp = (props) => {
4 return (
5 <div>
6 <ComponentA propA={props.propA}/>
7 <ComponentB propB={props.propB}/>
8 </div>
9 );
10};
11
12const ComponentA = (props) => {
13 return <div>{props.propA}</div>
14};
15
16const ComponentB = (props) => {
17 return <div>{props.propB}</div>
18};
答案是YES! 這是因為「MyApp」實際上被重新評估(或重新渲染)和「
这一概念也适用于基于类的 React 组件的渲染
方法。
React的作者意识到这并不总是所希望的结果,并且通过简单地比较旧和新特性来实现一些轻松的性能提高,然后再进行重新渲染......这基本上就是 React.memo和 React.PureComponent的设计!
让我们使用函数组件的备忘录(以后我们将看看基于类的组件):
1import React, { memo } from 'react';
2
3// 🙅♀️
4const ComponentB = (props) => {
5 return <div>{props.propB}</div>
6};
7
8// 🙆♂️
9const ComponentB = memo((props) => {
10 return <div>{props.propB}</div>
11});
你只需要用一个memo()
函数包装<ComponentB>
。现在它只会在propB
实际上改变值时重新渲染,无论其原始的渲染是多少次!
让我们来看看PureComponent,它基本上相当于备忘录,但它适用于基于类的组件。
1import React, { Component, PureComponent } from 'react';
2
3// 🙅♀️
4class ComponentB extends Component {
5 render() {
6 return <div>{this.props.propB}</div>
7 }
8}
9
10// 🙆♂️
11class ComponentB extends PureComponent {
12 render() {
13 return <div>{this.props.propB}</div>
14 }
15}
这些性能提高几乎太容易了!您可能会想知道为什么React组件不会自动包含这些内部防护措施,以防止过度的再渲染。
事实上,备忘录和PureComponent有一个隐藏的成本。由于这些助手比较旧/新特许,这实际上可能是其自身的性能瓶颈。例如,如果您的特许权非常大,或者您正在通过React组件作为特许权,旧/新特许权的比较可能是昂贵的。
银子子弹在编程的世界是罕见的! 备忘录/PureComponent 也不例外,你一定会想用精心思维的方式测试它们。
对于 React Hooks,请参阅 useMemo作为一种类似的方式来防止不必要的计算工作 <$>
避免匿名功能
组件的主要体内的函数通常是事件处理器或回复函数,在许多情况下,您可能会被诱惑为它们使用匿名函数:
1import React from 'react';
2
3function Foo() {
4 return (
5 <button onClick={() => console.log('boop')}> // 🙅♀️
6 BOOP
7 </button>
8 );
9}
由于匿名函数没有被分配一个标识符(通过 const/let/var
),因此,当这个功能组件不可避免地再次被渲染时,它们不会持久。
1import React, { useCallback } from 'react';
2
3// Variation 1: naming and placing handler outside the component
4const handleClick = () => console.log('boop');
5function Foo() {
6 return (
7 <button onClick={handleClick}> // 🙆♂️
8 BOOP
9 </button>
10 );
11}
12
13// Variation 2: "useCallback"
14function Foo() {
15 const handleClick = useCallback(() => console.log('boop'), []);
16 return (
17 <button onClick={handleClick}> // 🙆♂️
18 BOOP
19 </button>
20 );
21}
useCallback是避免匿名函数陷阱的另一种方法,但它有类似的交易,伴随着我们之前所讨论的React.memo
。
使用基于类的组件,该解决方案非常简单,实际上没有任何缺点,这是在 React 中定义处理器的推荐方法:
1import React from 'react';
2
3class Foo extends Component {
4 render() {
5 return (
6 <button onClick={() => console.log('boop')}> {/* 🙅♀️ */}
7 BOOP
8 </button>
9 );
10 }
11}
12
13class Foo extends Component {
14 render() {
15 return (
16 <button onClick={this.handleClick}> {/* 🙆♂️ */}
17 BOOP
18 </button>
19 );
20 }
21 handleClick = () => { // this anonymous function is fine used like this
22 console.log('boop');
23 }
24}
避免对象文字
此性能提示与有关匿名函数的上一节类似。 对象字母没有持久的内存空间,因此当组件重新渲染时,您的组件需要在内存中分配一个新的位置:
1function ComponentA() {
2 return (
3 <div>
4 HELLO WORLD
5 <ComponentB style={{ {/* 🙅♀️ */}
6 color: 'blue',
7 background: 'gold'
8 }}/>
9 </div>
10 );
11}
12
13function ComponentB(props) {
14 return (
15 <div style={this.props.style}>
16 TOP OF THE MORNING TO YA
17 </div>
18 )
19}
每当<ComponentA>
被重新渲染时,必须在内存中创建
一个新的对象,这也意味着<ComponentB>
实际上正在接收一个不同的风格
对象。
此提示不仅适用于风格
插件,而且通常在 React 组件中无意中使用对象字母。
可以通过命名对象来轻松解决这个问题(当然,除了组件的体外!):
1const myStyle = { // 🙆♂️
2 color: 'blue',
3 background: 'gold'
4};
5function ComponentA() {
6 return (
7 <div>
8 HELLO WORLD
9 <ComponentB style={myStyle}/>
10 </div>
11 );
12}
13
14function ComponentB(props) {
15 return (
16 <div style={this.props.style}>
17 TOP OF THE MORNING TO YA
18 </div>
19 )
20}
使用 React.lazy 和 React.Suspense
React 应用程序的快速化部分可以通过代码划分来实现. 此功能在 React v16 中引入了 React.lazy 和 React.Suspense。
如果你不知道,代码分割的概念是你的JavaScript客户端源(例如,你的React应用程序代码)被分割成较小的块,并且只以懒惰的方式加载这些块。
1- bundle.js (10MB!)
使用代码划分,这可能会导致包的初始网络请求显着小:
1- bundle-1.js (5MB)
2- bundle-2.js (3MB)
3- bundle-3.js (2MB)
最初的网络请求将只
需要下载5MB,它可以开始向最终用户展示一些有趣的东西。想象一个网站,最初只需要标题和脚印。一旦加载,它将开始请求包含实际博客帖子的第二包。
如何在React中进行代码分割?
如果你正在使用 Create React App,它已经配置为代码划分,所以你可以简单地使用 React.lazy 和 React.Suspense 离开门户! 如果你自己配置 webpack,它应该看起来像这个(https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269)。
以下是一個簡單的例子,其中懒惰和暫停被實施:
1import React, { lazy, Suspense } from 'react';
2import Header from './Header';
3import Footer from './Footer';
4const BlogPosts = React.lazy(() => import('./BlogPosts'));
5
6function MyBlog() {
7 return (
8 <div>
9 <Header>
10 <Suspense fallback={<div>Loading...</div>}>
11 <BlogPosts />
12 </Suspense>
13 <Footer>
14 </div>
15 );
16}
注意倒退
提示,这将立即显示给用户,而第 2 个包装部分正在加载(例如,
<$>[注] 查看此伟大的文章在 Code Splitting with React Suspense <$>
避免频繁的安装/卸载
很多时候,我们习惯了使用三级陈述(或类似的东西)来使组件消失:
1import React, { Component } from 'react';
2import DropdownItems from './DropdownItems';
3
4class Dropdown extends Component {
5 state = {
6 isOpen: false
7 }
8 render() {
9 <a onClick={this.toggleDropdown}>
10 Our Products
11 {
12 this.state.isOpen
13 ? <DropdownItems>
14 : null
15 }
16 </a>
17 }
18 toggleDropdown = () => {
19 this.setState({isOpen: !this.state.isOpen})
20 }
21}
由于从 DOM 删除
为了减轻这种情况,最好避免完全卸载组件,而可以使用某些策略,例如将CSS的透明度设置为零,或将CSS的可见性设置为无
。