React 性能优化

发布于 2024-12-10 18:31:45 字数 7089 浏览 6 评论 0

组件不更新则不重新渲染

当一个组件的 propsstate 变更,React 会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM。当它们不相同时,React 会更新该 DOM。

使用工具: shouldComponentUpdateReact.PureComponent

如果你的 React 组件渲染很慢,你可以通过覆盖生命周期方法 shouldComponentUpdate 来进行提速。该方法会在重新渲染前被触发。如果你知道在什么情况下你的组件不需要更新,你可以在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程,其包括该组件的 render 调用以及之后的操作。

其默认实现总是返回 true ,来让 React 执行更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

在大部分情况下,你可以继承 React.PureComponent 以代替手写 shouldComponentUpdate() 。它用当前与之前 propsstate浅比较 覆写了 shouldComponentUpdate() 的实现。

shouldComponentUpdate 的作用

这是一个 React 组件的子树。每个节点中, SCU 代表 shouldComponentUpdate 返回的值,而 vDOMEq 代表返回的 React 元素是否相同。最后,圆圈的颜色代表了该组件是否需要被调停。

节点 C2 的 shouldComponentUpdate 返回了 false ,React 因而不会去渲染 C2,也因此 C4 和 C5 的 shouldComponentUpdate 不会被调用到。

对于 C1 和 C3, shouldComponentUpdate 返回了 true ,所以 React 需要继续向下查询子节点。这里 C6 的 shouldComponentUpdate 返回了 true ,同时由于渲染的元素与之前的不同使得 React 更新了该 DOM。

最后一个有趣的例子是 C8。React 需要渲染这个组件,但是由于其返回的 React 元素和之前渲染的相同,所以不需要更新 DOM。

显而易见,你看到 React 只改变了 C6 的 DOM。对于 C8,通过对比了渲染的 React 元素跳过了渲染。而对于 C2 的子节点和 C7,由于 shouldComponentUpdate 使得 render 并没有被调用。因此它们也不需要对比元素了。

示例

如果你的组件只有当 props.color 或者 state.count 的值改变才需要更新时,你可以使用 shouldComponentUpdate 来进行检查:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

上面这段代码中, shouldComponentUpdate 仅检查了 props.colorstate.count 是否改变,如果这些值没有改变,那么这个组件不会更新。

如果你的组件更复杂一些,你可以使用类似“ 浅比较 ”的模式来检查 propsstate 中所有的字段,以此来决定是否组件需要更新。React 已经提供了一位好帮手来帮你实现这种常见的模式 - 你只要继承 React.PureComponent 就行了。所以上面这段代码可以改成以下这种更简洁的形式:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

使用 React.PureComponent 的注意点

React.PureComponent 做的仅仅是 浅比较 ,如果 新数据对象引用旧数据对象引用 相同,则不更新

大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate 。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。

当数据结构很复杂时,情况会变得麻烦。例如,你想要一个 ListOfWords 组件来渲染一组用逗号分开的单词。它有一个叫做 WordAdder 的父组件,该组件允许你点击一个按钮来添加一个单词到列表中。以下代码并不正确:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // 这部分代码很糟,而且还有 bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

问题在于 PureComponent 仅仅会对新老 this.props.words 的值进行简单的对比。由于代码中 WordAdderhandleClick 方法改变了同一个 words 数组,使得新老 this.props.words 比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染, ListOfWords 却并没有被更新。

解决方案

改变数据引用对象

避免该问题最简单的方式是避免更改你正用于 propsstate 的值。

例如,上面 handleClick 方法可以用 concat 或者 扩展运算符Object.assign 等重写:

handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
  // 或者
  this.setState(state => ({
    words: [...state.words, 'marklar'],
  }));
}

React.memo (类似 Vue 的 keep-alive ?)

允许组件在 props 没有改变的情况下跳过渲染,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

在使用 memo 缓存组件后,React 会对每一个 prop 使用 Object.is 比较新值和老值,返回 true ,表示没有变化。

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

特点

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

默认情况下 React.memo 只会对复杂对象做浅层对比(引用地址是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现(此方法仅作为 性能优化 的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug ):

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

注意 :与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等, areEqual 会返回 true ;如果 props 不相等,则返回 false 。这与 shouldComponentUpdate 方法的返回值相反。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

九命猫

暂无简介

文章
评论
26 人气
更多

推荐作者

李珊平

文章 0 评论 0

Quxin

文章 0 评论 0

范无咎

文章 0 评论 0

github_ZOJ2N8YxBm

文章 0 评论 0

若言

文章 0 评论 0

南…巷孤猫

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文