【React性能优化】父组件渲染如何避免子组件不必要的渲染

类组件:

  1. 需要注意的点是,尽量避免事件处理函数直接返回,如以下写法:
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题

应该用以下写法来写
1).构造器中绑定

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

2).class fields 语法

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  handleClick = () => {
    console.log('this is:', this);
  };
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
  1. 使用shouldComponentUpdate()
  2. 使用React.PureComponent 来代替手写 shouldComponentUpdate
    但它只进行浅比较(基本数据类型只比较值,复杂数据类型只比较引用地址),所以当 props 或者 state 某种程度是可变的话,浅比较可能会有遗漏。在给props重新赋值的时候,如果需要引起重新渲染,要赋一个新值,即引用地址需要改变

可能出现的问题:

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; //浅克隆,引用了相同的地址,可以使用lodash.deepClone()
    words.push('marklar');
    this.setState({words: words});
  }

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

以上代码中 WordAdder 的 handleClick 方法改变了同一个 words 数组,使得新老 this.props.words 比较的其实还是同一个数组。即便实际上数组中的单词已经变了,但是比较结果是相同的。可以看到,即便多了新的单词需要被渲染, ListOfWords 却并没有被更新。

解决办法:
避免该问题最简单的方式是避免更改你正用于 props 或 state 的值。例如,上面 handleClick 方法可以用 concat 重写:

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

ES6 数组支持扩展运算符,这让代码写起来更方便了。如果你在使用 Create React App,该语法已经默认支持了。

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

你可以用类似的方式改写代码来避免可变对象的产生。例如,我们有一个叫做 colormap 的对象。我们希望写一个方法来将 colormap.right 设置为 ‘blue’。我们可以这么写:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

为了不改变原本的对象,我们可以使用 Object.assign 方法:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

现在 updateColorMap 返回了一个新的对象,而不是修改老对象。Object.assign 是 ES6 的方法,需要 polyfill。

这里有一个 JavaScript 的提案,旨在添加对象扩展属性以使得更新不可变对象变得更方便:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

函数组件:

  1. React.memo - HOC(高阶组件)
import React from 'react';

const ChildComponent = React.memo(function({ prop }) {
  // 子组件渲染逻辑
});

export default ChildComponent;

也是对props进行浅比较,如果props没有改变,子组件就不重新渲染。当改变数组(对象)类型的props的时候记得返回一个全新的数组(对象),或者如果只在某些props改变的时候需要渲染,可以加上第二个参数(相当于PureComponent和shouldComponentUpdate的结合),如下,当areEqual为false的时候会触发渲染。

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);
  1. useMemo() 进行细粒度性能优化
    上面 React.memo() 的使用我们可以发现,最终都是在最外层包装了整个组件,并且需要手动写一个方法比较那些具体的 props 不相同才进行 re-render。

而在某些场景下,我们只是希望 component 的部分不要进行 re-render,而不是整个 component 不要 re-render,也就是要实现局部 Pure 功能。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

import { Button, Divider } from "antd";
import React, { useState, useMemo } from "react";

const Parent = () => {
const [num, setNum] = useState(0);

const clickHadler = () => {
setNum(num + 1)
}

// 使用 useMemo 缓存计算的结果
const computeResult = useMemo(() => {
   for(let i = 0; i < 10000; i++) {

   }
   console.log('进行了大量计算')
}, [])

return (
<>
{computeResult}
number值: {num}
<Button type='primary' onClick={() => clickHadler()}>
点击计算
</>
);
};

export default Parent;
  1. useCallback
 const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值