react 常见性能优化

2023-10-08  本文已影响0人  暴躁程序员

一、避免不必要的 render 执行

当父组件的任意状态(state或props)发生变更时,父组件会重新渲染(执行render),同时依赖父组件的所有子组件也都会重新渲染(执行render),但是我们希望只有当父组件传递给子组件的 props 发生变更时,子组件才会重新渲染

1. 解决方式一:使用 shouldComponentUpdate 优化(只适用于类组件)

需要 immutable 脚本库配合来判断 state 和 props 是否变化,变化执行render,不变化不执行 render

  1. 在父组件中使用 shouldComponentUpdate
import React, { Component } from "react";
import OptimizeChildView from "./OptimizeChildView"
import { Map,is } from 'immutable'; // 可用来判断对象的值是否相等

class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {
      height: 170,
      age: 20,
    };
    this.changeAge = this.changeAge.bind(this) /* 修正this指向 */
    this.changeHeight = this.changeHeight.bind(this) /* 修正this指向 */
    this.changeHeightNo = this.changeHeightNo.bind(this) /* 修正this指向 */
  }
  render() {
    console.log('父组件 render');
    return (
      <>
        <h1>父组件</h1>
        <div>{this.state.age}</div>
        <div>{this.state.height}</div>
        <button onClick={this.changeAge}>改变当前组件的state、改变传入子组件的props</button>
        <button onClick={this.changeHeight}>改变当前组件的state、不改变传入子组件的props</button>
        <button onClick={this.changeHeightNo}>不改变当前组件的state、不改变传入子组件的props</button>
        <hr />
        <OptimizeChildView age={this.state.age} />
      </>
    );
  }
  // 结果:父子组件 render 都会执行(√)
  changeAge(e) {
    this.setState((prevState) => {
      return { age: prevState.age + 1 };
    });
  }
  // 结果:父组件render会执行,子组件render不会执行(√)
  changeHeight(e) {
    this.setState((prevState) => {
      return { height: prevState.height + 1 };
    });
  }
  // 结果:父子组件 render 都不会执行(√)
  changeHeightNo(e) {
    this.setState((prevState) => {
      return { height: prevState.height };
    });
  }
  // shouldComponentUpdate 优化
  shouldComponentUpdate(nextProps,nextState){
    const that = this
    if(is(Map(nextProps),Map(that.props)) && is(Map(nextState),Map(that.state))){
      return false // 代表父组件传过来的props和当前组件的state都没变化,不执行 render
    }
    return true
  }
}
export default Test;
  1. 在子组件中使用 shouldComponentUpdate
import React, { Component } from "react";
import { Map,is } from 'immutable'; // 可用来判断对象的值是否相等
class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    console.log('子组件 render');
    return (
      <> 
        <h1>子组件</h1>
        <div>{this.props.age}</div>
      </>
    );
  }
  // 代表父组件传过来的props和当前组件的state都没变化,不执行 render
  shouldComponentUpdate(nextProps,nextState){
    const that = this
    if(is(Map(nextProps),Map(that.props)) && is(Map(nextState),Map(that.state))){
      return false 
    }
    return true
  }
}
export default Test;

2. 解决方式二:使用 PureComponent 优化 (只适用于类组件)

React.PureComponent 组件采用对属性和状态用浅比较的方式在组件内部实现了 shouldComponentUpdate()

  1. 在父组件中使用 PureComponent
import React, { PureComponent } from "react";
import OptimizeChildView from "./OptimizeChildView"

class Test extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      height: 170,
      age: 20,
    };
    this.changeAge = this.changeAge.bind(this) /* 修正this指向 */
    this.changeHeight = this.changeHeight.bind(this) /* 修正this指向 */
    this.changeHeightNo = this.changeHeightNo.bind(this) /* 修正this指向 */
  }
  render() {
    console.log('父组件 render');
    return (
      <>
        <h1>父组件</h1>
        <div>{this.state.age}</div>
        <div>{this.state.height}</div>
        <button onClick={this.changeAge}>改变当前组件的state、改变传入子组件的props</button>
        <button onClick={this.changeHeight}>改变当前组件的state、不改变传入子组件的props</button>
        <button onClick={this.changeHeightNo}>不改变当前组件的state、不改变传入子组件的props</button>
        <hr />
        <OptimizeChildView age={this.state.age} />
      </>
    );
  }
  // 结果:父子组件 render 都会执行(√)
  changeAge(e) {
    this.setState((prevState) => {
      return { age: prevState.age + 1 };
    });
  }
  // 结果:父组件render会执行,子组件render不会执行(√)
  changeHeight(e) {
    this.setState((prevState) => {
      return { height: prevState.height + 1 };
    });
  }
  // 结果:父子组件 render 都不会执行(√)
  changeHeightNo(e) {
    this.setState((prevState) => {
      return { height: prevState.height };
    });
  }
}
export default Test;
  1. 在子组件中使用 PureComponent
import React, { PureComponent } from "react";
class Test extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    console.log('子组件 render');
    return (
      <> 
        <h1>子组件</h1>
        <div>{this.props.age}</div>
      </>
    );
  }
}
export default Test;

3. 解决方式三:使用 React.memo 优化 (只适用于无状态组件)

React.memo 为高阶组件,需要传入两个参数,第一个参数是被缓存的组件,第二个参数是回调函数,只有当第二个回调函数返回true时才会重新渲染缓存组件
一般不传第二个参数(不传:只有当子组件依赖父组件的props发生变化才重新渲染子组件,传:状态变化满足某个条件才重新渲染子组件)

  1. 在父组件中
import { useState } from "react";
import OptimizeChildView from "./OptimizeChildView";
function OptimizeView() {
  const [height, setHeight] = useState(170);
  const [age, setAge] = useState(20);

  // 结果:父子组件 render 都会执行(√)
  function changeAge(e) {
    setAge(age + 1);
  }
  // 结果:父组件render会执行,子组件render不会执行(√)
  function changeHeight(e) {
    setHeight(height + 1);
  }
  // 结果:父子组件 render 都不会执行(√)
  function changeHeightNo(e) {
    setHeight(height);
  }
  console.log('父组件 render');
  return (
    <>
      <h1>父组件</h1>
      <div>{age}</div>
      <div>{height}</div>
      <button onClick={changeAge}>
        改变当前组件的state、改变传入子组件的props
      </button>
      <button onClick={changeHeight}>
        改变当前组件的state、不改变传入子组件的props
      </button>
      <button onClick={changeHeightNo}>
        不改变当前组件的state、不改变传入子组件的props
      </button>
      <hr />
      <OptimizeChildView age={age} />
    </>
  );
}

export default OptimizeView;
  1. 在子组件中使用 memo 高阶函数包裹组件
import { memo } from "react";
function OptimizeChildView(props) {
  console.log('子组件 render');
  return (
    <>
      <h1>子组件</h1>
      <div>{props.age}</div>
    </>
  );
}

export default memo(OptimizeChildView);

二、避免在 render 函数中使用内联函数和在 render 函数中使用bind绑定this

否则每次执行 render 函数都会创建新的函数实例

  1. 解决方式:使用非内联的方式定义点击函数,在 constructor 中使用bind绑定this
import React, { Component } from "react";
import OptimizeChildView from "./OptimizeChildView";
class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "zhangsan",
      age: 20,
    };
    this.changeAge = this.changeAge.bind(this) /* 修正this指向 */
  }

  render() {
    return (
      <>
        <div>{this.state.name}</div>
        <div>{this.state.age}</div>
        <button onClick={this.changeAge}>函数的正确打开方式</button>
        <button onClick={(e) => {this.setState((prevState) => {return { age: prevState.age + 1 }})}}>内联函数</button>
        <OptimizeChildView />
      </>
    );
  }
  changeAge(e) {
    this.setState((prevState) => {
      return { age: prevState.age + 1 };
    });
  }
}
export default Test;

三、避免创建不必要的组件最外层元素标签

  1. 解决方式:使用 <></> 代替 <div></div>充当组件最外层元素标签
import React, { Component } from "react";

class Test extends Component {
  /* render() {
    return (
      <div> 
        <div>hello world</div>
        <div>hello world</div>
      </div>
    );
  } */

  // 避免生成多余的外层 div
  render() {
    return (
      <> 
        <div>hello world</div>
        <div>hello world</div>
      </>
    );
  }
}
export default Test;
上一篇下一篇

猜你喜欢

热点阅读