提高 React 应用程序性能的 5 个技巧

您的 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.memoReact.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 删除 ,它可能会导致浏览器的 repaint/reflow

为了减轻这种情况,最好避免完全卸载组件,而可以使用某些策略,例如将CSS的透明度设置为零,或将CSS的可见性设置为

Published At
Categories with 技术
Tagged with
comments powered by Disqus