• 使用PureComponent或React.memo构建组件
  • 使用shouldComponentUpdate生命周期钩子
  • 渲染列表时使用key
  • 使用useCallback和useMemo缓存函数和变量

# shouldComponentUpdate()

shouldComponentUpdate(nextProps, nextState)

根据 shouldComponentUpdate() 的返回值,来决定组件是否重新渲染。 默认返回true,也就是当 props 或 state 发生变化时,都会重新渲染。

不建议自己实现 shouldComponentUpdate,建议使用 PureComponent 和 React.memo。

Note:

  1. 非要使用 shouldComponentUpdate, 一定配合不可变值一起使用,也是就是跟新state时,不能直接修改,必须调用setState()。
  2. 不建议在 shouldComponentUpdate 中做深比较。

# PureC

omponent

class Greeting extends React.PureComponent {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。 如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。 仅在你的 props 和 state 较为简单时,才使用 React.PureComponent, 或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。 你也可以考虑使用 immutable 对象加速嵌套数据的比较。

# React.memo()

React.memo is a higher order component.

const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

它与 React.PureComponent 非常相似,但只适用于函数组件,而不适用 class 组件。

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

# 使用 useMemo 缓存大量计算

又是渲染是不可避免的,但如果你的组件是一个功能组件,重新渲染会导致每次都调用大型计算函数,这是非常消耗性能的,我们可以使用 useMemo 来缓存计算结果,这样只有当输入的内容发生变化时,才会重新调用函数计算结果。

通过这种方式,我们达到空间换时间的策略,减少在一帧的工作时间内,js 线程执行时间不影响 GUI 线程,从而提高性能。

import { useMemo } from "react";

// 避免这样做
function Component(props) {
  const someProp = heavyCalculation(props.item);
  return <AnotherComponent someProp={someProp} />;
}

// 只有 `props.item` 改变时someProp的值才会被重新计算
function Component(props) {
  const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
  return <AnotherComponent someProp={someProp} />;
}

# 避免使用内联对象

在 JSX 中创建一个内联对象的时候,每次重新渲染都会重新生成一个新的对象,如果这里还存在了引用关系的话,会大大增加性能损耗,所以尽量避免使用内联对象。

import React from "react";

// Don't do this!
function Component(props) {
  const aProp = { someProp: "someValue" };
  return <AnotherComponent style={{ margin: 0 }} {...aProp} />;
}

// Do this instead :)
const styles = { margin: 0 };
function Component(props) {
  const aProp = { someProp: "someValue" };
  return <AnotherComponent style={styles} {...aProp} />;
}

# 避免使用匿名函数

虽然匿名函数可以更加方便的对函数进行传参,但是同内联对象一样,每一次重新渲染都会生成一个新的函数,所以我们应该尽量避免使用内联函数。

import React from "react";

// 避免这样做
function Component(props) {
  return <AnotherComponent onChange={() => props.callback(props.id)} />;
}

// 优化方法一
function Component(props) {
  const handleChange = useCallback(() => props.callback(props.id), [props.id]);
  return <AnotherComponent onChange={handleChange} />;
}

// 优化方法二
class Component extends React.Component {
  handleChange = () => {
    this.props.callback(this.props.id);
  };
  render() {
    return <AnotherComponent onChange={this.handleChange} />;
  }
}

# 延迟加载不是立即需要的组件

在加载的角度上说,当前不需要的组件不应该加载,我们应该尽量做到按需加载,通过利用 React.lazyReact.Suspense 可以轻松完成按需加载。

import React from "react";

// 延迟加载不是立即需要的组件
const MUITooltip = React.lazy(() => import("@material-ui/core/Tooltip"));

function Tooltip({ children, title }) {
  return (
    <React.Suspense fallback={children}>
      <MUITooltip title={title}>{children}</MUITooltip>
    </React.Suspense>
  );
}

function Component(props) {
  return (
    <Tooltip title={props.title}>
      <AnotherComponent />
    </Tooltip>
  );
}

# 调整 CSS 而不是强制组件加载和卸载

渲染成本很高,尤其是在需要更改 DOM 时。例如想要一次只能看到一个项目时,你可能想要卸载不可见的组件,并在它变得可见时将其重新加载。如果加载/卸载的组件“很重”,则此操作可能非常消耗性能并可能导致延迟。在这些情况下,最好通过 CSS 隐藏它,同时将内容保存到 DOM。

有时在保持组件加载的同时通过 CSS 隐藏可能是有益的,而不是通过卸载来隐藏。对于具有显著的加载/卸载时序的重型组件而言,这是有效的性能优化手段。

import { useState } from "react";

// 避免对大型的组件频繁对加载和卸载
function Component(props) {
  const [view, setView] = useState("view1");
  return view === "view1" ? <SomeComponent /> : <AnotherComponent />;
}

// 使用该方式提升性能和速度
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };

function DemoComponent() {
  const [visible, setVisible] = useState(false);

  return (
    <>
      <Component1 style={visible ? visibleStyles : hiddenStyles} />
    </>
  );
}

# 使用 Fragment 来避免添加额外的 DOM 节点

有些情况下,组件需要返回多个节点,但是一个函数只允许有一个返回值,如果是使用一个 div 进行包裹,那么一个完整的应用程序则会添加太多额外的无用标签,随着标签越来越多,加载速度也越来越慢。我们可以通过使用 Fragment 来避免创建不必要的元素。

function Component() {
  return (
    <>
      <h1>Hello world!</h1>
      <h1>Hello there!</h1>
      <h1>Hello there again!</h1>
    </>
  );
}