Skip to content

React 性能优化 #39

Open
Open
@myLightLin

Description

@myLightLin

当我们谈 React 性能优化的时候,通常是在谈对 组件性能 进行优化。像一些普适的性能优化思路,比如资源加载优化,代码压缩,减少重绘与回流,启用 CDN 等等都是通用的。而我们写 React,其实就是在跟组件打交道,其它框架层面的优化,React 都帮我们搞定了。作为开发者,我们需要对自己的 React 组件代码适当运用一些优化手段。那么对组件来说,核心思路只有一个:避免重复渲染。与此有关的 API 或生命周期方法有:useCallbackuseMemoReact.memoReact.PureComponentshouldComponentUpdate。这其中,有些是应用在类组件的,有些是在函数式组件里使用的。

类组件的性能优化

对于类组件来说,主要是 shouldComponentUpdate ,这个生命周期方法返回一个布尔值,React 会根据这个值决定是否执行后续的生命周期方法,进而决定组件是否重新渲染。它默认返回 true,表示无条件渲染;我们可以在这个方法里书写逻辑,当 props 或 state 无变化时返回 false,来阻止掉冗余的渲染。举个例子:

class A extends React.Component {
  state = {
    text1: '1',
    text2: '2'
  }
  render() {
    return (
      <div>
        <B text={this.state.text1} />
        <C text={this.state.text2} />
      </div>
    )
  }
}

现在我们有父组件 A,子组件 B 和 C。其中子组件的 text 数据是由父组件通过 props 传入的。我们知道,在 React 里,一旦父组件更新了,所有关联的子组件也会跟着更新。这带来的问题是:上面的例子我如果只想更新 text1 ,影响其实是 B 组件,但是因为所有组件都刷新了,C 组件的 text 没有发生变化,但它也跟着渲染更新了。所以这时我们就可以在 C 组件里添加 shouldComponentUpdate 的逻辑:

shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.text === this.props.text) {
    return false
  }
  return true
}

通过比较更新前后 props 的 text 属性是否相等,来决定是否重新渲染 C 组件。其实 React 有个类可以专门来做这件事,这就是React.PureComponent ,这个类相当于在 shouldComponentUpdate 里帮我们把优化工作做好了,但是它只会浅比较组件的 props 和 state ,所谓浅比较就是对基本数据类型比较值是否相等,而对引用类型则比较引用是否相等。这会带来问题:数据内容不变,但是引用变了;或者数据引用变了,但是内容没变,这两种情况都会导致浅比较出现异常,导致渲染问题。所以我们一般会借助 Immutable.js 这个库来帮助创建不可变数据,这个库提供的 API 可以创建前后引用不相等的数据,这样就可以确保 PureComponent 的比较逻辑正常工作。

import { List } from 'immutable'

const emptyList = List()
const addList = emptyList.push(1)  // [1]
console.log(emptyList === addList)  // false

class HelloWorld extends React.PureComponent {
  state = {}

  constructor() {}
   
  render() {}
}

函数式组件性能优化

类组件的 shouldComponentUpdate/PureComponent 对应到函数式组件,就是 React.memo API,这个函数是 React 提供一个高阶组件,它接收两个参数:

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

第一个函数是我们写的组件,第二个参数是比较函数,React 会根据第二个参数的比较结果,决定是否复用函数的渲染缓存结果,如果不传第二个参数,默认会进行 props 的浅比较,效果相当于 PureComponent。值得一提的是,React.memo 只能对 props 进行比较,无法感知组件内部 state 的变化。

React.memo 是对整个组件进行缓存优化,那如果我们只想对函数中的某一段逻辑进行缓存优化呢?这时就要靠 useMemouseCallback 来帮忙了,这两个都是 React Hooks。

useMemo 的用法如下:

const cachedValue = useMemo(calculateValue, dependencies)

它接收两个参数,第一个参数是个计算值,比如你传入函数,函数的返回值会被缓存;第二个参数是依赖项,只有依赖发生变化时才会重新执行逻辑计算新的值。

useCallback 的用法如下:

const cachedFn = useCallback(fn, dependencies)

它接收两个参数:第一个参数是个函数,第二个参数是依赖项。那 useMemouseCallback 有啥区别呢?

  • useMemo 缓存的是,也就是函数返回的任意值都会被缓存
  • useCallback 缓存的是 整个函数,也就是你通过 useCallback 包装,让这个函数有了缓存结果
  • 也就是说,useCallback 是函数版的 useMemo,它作用的是函数。

总结

  • React 性能优化的思路是避免组件重复渲染
  • 对于类组件,可以通过 shouldComponentUpdate 生命周期以及 React.PureComponent 基类优化
  • 对于函数式组件,可以通过 React.memouseMemouseCallback 优化

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions