React性能优化笔记

251 阅读4分钟

1、函数组件性能优化

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 方法的返回值相反

2、类使用React.PureComponent进行优化,但是它的缺陷只能识别到数据变化才会更新组件

以下方式可以让数组和字典变成新的数据让子组件可以达到刷新效果 对数组重写监听属性发生改变

// 数组增加字段
handleClick() {
  this.setState(state => ({
    words: state.words.concat(['marklar'])
  }));
  // 使用ES6扩展运算符优化
  this.setState(state => ({
    words: [...state.words, 'marklar']
  }));
}

字典新增改变原本的对象

function updateColorMap(colormap) {
  colormap.right = 'blue';
  // 不改变原来对象,返回一个新的对象,而不是修改老对象
  return Object.assign({}, colormap, {right: 'blue'});
  // ES6语法
  return {...colormap, right: 'blue'};
}

UI层的问题定位解决

通过插件React Developer ToolsComponents 筛选出错误的代码块,直接定位代码错误位置;通过双击会给出具体组件具体错误原因

image.png 这个问题就是Form.Item默认数据属性写错误了,得用initialValues替代defalutValues image.png

组件性能优化3个关键核心

1.使用shouldComponentUpdate 规避冗余的更新逻辑

shouldComponentUpdate(nextProps, nextState)

React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染) 默认值为true,也就是“无条件re-render”。

问题点: 在 React 中,只要父组件发生了更新,那么所有的子组件都会被无条件更新。同样,当自身调用setState后,不管setState前后的状态内容是否真的改变,它都会走一遍更新流程

shouldComponentUpdate(nextProps, nextState) {
  // 判断 text 属性在父组件更新前后有没有发生变化,若没有发生变化,则返回 false
  if(nextProps.text === this.props.text) {
    return false
  }
  // 只有在 text 属性值确实发生变化时,才允许更新进行下去
  return true
}

2.PureComponent + Immutable.js

所谓“持久性数据”,指的是这个数据只要被创建出来了,就不能被更改。我们对当前数据的任何修改动作,都会导致一个新的对象的返回。这就将数据内容的变化和数据的引用严格地关联了起来,使得“变化”无处遁形。

// 引入 immutable 库里的 Map 对象,它用于创建对象
import { Map } from 'immutable'

// 初始化一个对象 baseMap
const baseMap = Map({
  name: '修言',
  career: '前端',
  age: 99
})

// 使用 immutable 暴露的 Api 来修改 baseMap 的内容
const changedMap = baseMap.set({
  age: 100
})

// 我们会发现修改 baseMap 后将会返回一个新的对象,这个对象的引用和 baseMap 是不同的
console.log('baseMap === changedMap', baseMap === changedMap)

PureComonent 和 Immutable.js 真是一对好基友!在实际的开发中,我们也确实经常左手 PureComonent,右手 Immutable.js,研发质量大大地提升呀.并不是所有场景下都可以作为最优解被团队采纳。因此,一些团队也会基于 PureComonent 和 Immutable.js 去打造将两者结合的公共类,通过改写 setState 来提升研发体验,这也是不错的思路

3.React.memo 与 useMemo

使用 useMemo,我们可以对函数组件的执行逻辑进行更加细粒度的管控(尤其是定向规避掉一些高开销的计算),同时也弥补了 React.memo 无法感知函数内部状态的遗憾,这对我们整体的性能提升是大有裨益的。

import React from "react";
// 定义一个函数组件

function FunctionDemo(props) {
  return xxx
}

// areEqual 函数是 memo 的第二个入参,我们之前放在 shouldComponentUpdate 里面的逻辑就可以转移至此处

function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}

// 使用 React.memo 来包装函数组件
export default React.memo(FunctionDemo, areEqual);

React.memo 会帮我们“记住”函数组件的渲染结果,在组件前后两次 props 对比结果一致的情况下,它会直接复用最近一次渲染的结果。之前我们在 shouldComponentUpdate 里面做的事情,现在就可以放在 areEqual 里来做。

PureComponent 等价于 React.memo会自动为你的组件执行 props 的浅比较逻辑。和shouldComponentUpdate 不同的是,React.memo 只负责对比 props,而不会去感知组件内部状态(state)的变化。

简而言之,React.memo 控制是否需要重渲染一个组件,而 useMemo 控制的则是是否需要重复执行某一段逻辑。

// 把目标逻辑作为第一个参数传入,把逻辑的依赖项数组作为第二个参数传入。这样只有当依赖项数组中的某个依赖发生变化时,useMemo 才会重新执行第一个入参中的目标逻辑.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
import React,{ useMemo } from "react";

export default function ChildB({text, count}) {
  console.log("ChildB 的render 逻辑执行了");
  // text 文本的渲染逻辑
  const renderText = (text)=> {
    console.log('renderText 执行了')
    return <p>
    子组件B的文本内容:
      {text}
  </p>
  }

  // count 数字的渲染逻辑
  const renderCount = (count) => {
    console.log('renderCount 执行了')
    return <p>
      子组件B的数字内容:
        {count}
    </p>
  }
  
  // 使用 useMemo 加持两段渲染逻辑
  const textContent = useMemo(()=>renderText(text),[text])
  const countContent = useMemo(()=>renderCount(count),[count])
  return (
    <div className="childB">
      {textContent}
      {countContent}
    </div>
  );
}