前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】改善React应用性能的5个建议

【译】改善React应用性能的5个建议

作者头像
张张
发布2020-03-23 17:30:22
1.3K0
发布2020-03-23 17:30:22
举报

你的 React 应用是否感到有些迟缓?你是否害怕在 Chrome DevTools 中打开 “paint flash”?试试这 5 个性能技巧吧!

本文包含有关 React 开发的 5 条性能建议。

1.使用 memo 和 PureComponent

考虑下面这个简单的 React 应用程序,您是否认为当 props.propA 更改值时 <ComponentB> 会重新渲染?

代码语言:javascript
复制
import React from "react";

const MyApp = props => {
  return (
    <div>
      <ComponentA propA={props.propA} />
      <ComponentB propB={props.propB} />
    </div>
  );
};

const ComponentA = props => {
  return <div>{props.propA}</div>;
};

const ComponentB = props => {
  return <div>{props.propB}</div>;
};

答案是肯定的!这是因为 MyApp 实际上是重新计算运行(或者重新渲染 ?)了,而 <ComponentB> 也在里面。所以即使它自己的 props 没有改变,它的父组件也会导致它重新渲染。

这个概念也适用于基于类的 React 组件的渲染方式。

React 的作者意识到这并不是一个理想的结果,在重新渲染前简单地比较新旧 props 可以获得一些简单的性能提升…这就是 React.memoReact.PureComponent 的设计初衷!

memo

让我们将 memo 与 function 组件一起使用(我们将在之后研究基于 Class 的组件):

代码语言:javascript
复制
import React, { memo } from "react";

// ?‍♀️
const ComponentB = props => {
  return <div>{props.propB}</div>;
};

// ?‍♂️
const ComponentB = memo(props => {
  return <div>{props.propB}</div>;
});

就这样!您只需要用 memo() 函数包装 <ComponentB>。现在,仅在 propB 实际更改值时才重新渲染,而不管其父级重新渲染多少次!

PureComponent

让我们看看 PureComponent。它本质上等同于 memo,但它是针对基于 Class 的组件的。

代码语言:javascript
复制
import React, { Component, PureComponent } from "react";

// ?‍♀️
class ComponentB extends Component {
  render() {
    return <div>{this.props.propB}</div>;
  }
}

// ?‍♂️
class ComponentB extends PureComponent {
  render() {
    return <div>{this.props.propB}</div>;
  }
}

这些性能提升几乎太容易了!您可能想知道为什么 React 组件不会自动包含这些内部保护措施以防止过度渲染。

实际上,memoPureComponent 有一个隐藏的代价,由于这些义务比较新旧 props,这实际上可能导致其自身的性能瓶颈,例如,如果您的 props 非常大,或者您将 React 组件作为 props 传递,那么比较新旧 props 的成本代价可能很高。

在编程的世界里,银弹是罕见的!且 memo/PureComponent 也不例外。您肯定会想以一种审慎周到的方式驾驭它们。在某些情况下,它们可以让您惊讶地发现它们可以节省多少计算时间。

对于 React Hooks,可以使用 useMemo 作为类似的方法来防止不必要的计算工作

2.避免匿名函数

组件主体内部的函数通常是事件处理程序或回调。在许多情况下,您可能会为它们使用匿名函数:

代码语言:javascript
复制
import React from "react";

function Foo() {
  return <button onClick={() => console.log("boop")}> // ?‍♀️ BOOP</button>;
}

由于没有为匿名函数分配标识符(通过 const/let/var),因此每当不可避免地再次渲染此功能组件时,它们就不会被持久化(persistent)。这会导致 JavaScript 在每次重新渲染此组件时重新分配新的内存,而不是在使用“命名函数”时分配的内存:

代码语言:javascript
复制
import React, { useCallback } from "react";

// 变体1:在组件外部命名和放置处理程序
const handleClick = () => console.log("boop");
function Foo() {
  return <button onClick={handleClick}> // ?‍♂️ BOOP</button>;
}

// 变体2: "useCallback"
function Foo() {
  const handleClick = useCallback(() => console.log("boop"), []);
  return <button onClick={handleClick}> // ?‍♂️ BOOP</button>;
}

useCallback 是另一种避免匿名函数缺陷的方法,但是它也有类似的折衷权衡,这与我们前面介绍的React.memo 一样。

使用基于 class 的组件,解决方案非常简单,并且没有任何缺点,这是在 React 中定义处理程序的推荐方法:

代码语言:javascript
复制
import React from "react";

class Foo extends Component {
  render() {
    return (
      <button onClick={() => console.log("boop")}>
        {" "}
        {/* ?‍♀️ */}
        BOOP
      </button>
    );
  }
}

class Foo extends Component {
  render() {
    return (
      <button onClick={this.handleClick}>
        {" "}
        {/* ?‍♂️ */}
        BOOP
      </button>
    );
  }
  handleClick = () => {
    // 这个匿名函数很好用
    console.log("boop");
  };
}

3.避免对象字面量

此性能提示与上一节有关匿名函数的部分相似。对象字面量没有持久的存储空间,因此只要组件重新渲染,您的组件就需要在内存中分配新的位置:

代码语言:javascript
复制
function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={{  {/* ?‍♀️ */}
        color: 'blue',
        background: 'gold'
      }}/>
    </div>
  );
}

function ComponentB(props) {
  return (
    <div style={this.props.style}>
      TOP OF THE MORNING TO YA
    </div>
  )
}

每次重新渲染 <ComponentA> 时,都必须在内存中“创建”新的对象常量。此外,这还意味着 <ComponentB> 实际上正在接收其他样式对象。使用 memoPureComponent 甚至都无法阻止在此重新渲染 ?。

本技巧不仅适用于样式 props ,而且通常是在 React 组件中不经意使用对象字面量的地方。

可以通过命名对象(当然在组件主体之外!)来轻松解决此问题:

代码语言:javascript
复制
const myStyle = {
  // ?‍♂️
  color: "blue",
  background: "gold"
};
function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={myStyle} />
    </div>
  );
}

function ComponentB(props) {
  return <div style={this.props.style}>TOP OF THE MORNING TO YA</div>;
}

4. 使用 React.lazyand 和 React.Suspense

让 React 应用程序快速运行的一部分可以通过代码拆分来完成。此功能是通过 React v16 引入的 React.lazyReact.Suspense实现。

如果您不知道,代码分割的概念是将 JavaScript 客户端源代码(例如,React 应用程序代码)分成更小的块,并且只以一种惰性的方式加载这些块,如果没有任何代码拆分,单个包可能非常大:

代码语言:javascript
复制
- bundle.js (10MB!)

使用代码分割,能对 bundle 的初始网络请求大大减少:

代码语言:javascript
复制
- bundle-1.js (5MB)
- bundle-2.js (3MB)
- bundle-3.js (2MB)

最初的网络请求将“仅”需要下载 5MB,并且可以开始向最终用户显示一些有趣的内容,想象一下一个博客网站,最初只需要页眉和页脚。加载后,它将开始请求包含实际博客文章的第二个 bundle。这是一个简单的示例,可以方便地进行代码分割。 ???

如何在 React 中完成代码分割?

如果您使用的是 create-react-app,则已经对其进行了代码拆分配置,因此您可以轻松使用 React.lazyReact.Suspense !如果您是自己配置 Webpack,则应如下所示。

代码语言:javascript
复制
module.exports = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    // `filename` provides a template for naming your bundles (remember to use `[name]`)
    filename: "[name].bundle.js",
    // `chunkFilename` provides a template for naming code-split bundles (optional)
    chunkFilename: "[name].bundle.js",
    // `path` is the folder where Webpack will place your bundles
    path: "./dist",
    // `publicPath` is where Webpack will load your bundles from (optional)
    publicPath: "dist/"
  }
};

下面是一个实现 lazy 和 Suspense 的简单示例:

代码语言:javascript
复制
import React, { lazy, Suspense } from 'react';
import Header from './Header';
import Footer from './Footer';
const BlogPosts = React.lazy(() => import('./BlogPosts'));

function MyBlog() {
  return (
    <div>
      <Header>
      <Suspense fallback={<div>Loading...</div>}>
        <BlogPosts />
      </Suspense>
      <Footer>
    </div>
  );
}

注意 fallback prop,加载第二个 bundle 程序块(例如,<BlogPosts>)时,将立即向用户显示此内容。

5.避免频繁的 Mounting/Unmounting

很多时候,我们习惯于使用三元语句(或类似的语句)使组件消失:

代码语言:javascript
复制
import React, { Component } from 'react';
import DropdownItems from './DropdownItems';

class Dropdown extends Component {
  state = {
    isOpen: false
  }
  render() {
    <a onClick={this.toggleDropdown}>
      Our Products
      {
        this.state.isOpen
          ? <DropdownItems>
          : null
      }
    </a>
  }
  toggleDropdown = () => {
    this.setState({isOpen: !this.state.isOpen})
  }
}

由于 <DropdownItems> 已从 DOM 中删除,因此可能导致浏览器重绘/重排。这些可能很昂贵,尤其是如果它导致其他 HTML 元素移动时。

为了减轻这种情况,建议避免完全卸载组件。相反,您可以使用某些策略,例如将 CSS 不透明度设置为零,或将 CSS 可见性设置为“null”。这将使组件保留在 DOM 中,同时使其有效地消失而不会产生任何性能代价。


原文:https://alligator.io/react/keep-react-fast/

作者:William Le

译者:Dunizb

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.使用 memo 和 PureComponent
    • memo
      • PureComponent
      • 2.避免匿名函数
      • 3.避免对象字面量
      • 4. 使用 React.lazyand 和 React.Suspense
        • 如何在 React 中完成代码分割?
        • 5.避免频繁的 Mounting/Unmounting
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档