React & React Native 渲染优化神器:why-did-you-render

2,112 阅读3分钟

近期项目遇到了一些性能瓶颈,其中就包括组件重复渲染的问题。就在我为如何定位组件重复渲染问题发愁时,突然想起了之前看过别人文章推荐过一个库:why-did-you-render

于是抱着试一试的想法试了一下,发现是真香啊。话不多说,我们来看下如何使用这个神器以及其使用效果是什么样的。

安装

这个库适用于 React 以及 React Native。安装很简单,和其他 npm 库一样安装即可。

使用 npm:

npm install @welldone-software/why-did-you-render --save-dev

使用 yarn:

yarn add --dev @welldone-software/why-did-you-render

使用 pnpm:

pnpm add -D -w @welldone-software/why-did-you-render

初始化

在安装成功之后,首先我们需要创建一个 wdyr.js 配置文件。

// wdyr.js
if (__DEV__) {
  const React = require('react');
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React);
}

要特别注意,千万别在生产环境中引入这个库!

创建配置文件后,需要在你应用入口文件的最开始被 import:

React:

import './wdyr'; // <--- first import

import React from 'react';
import App from './App';


ReactDOM.render(<App/>, document.getElementById('root'));

React Native:

import './wdyr'; // <--- first import

import { AppRegistry } from 'react-native';  
import App from './App';
  
AppRegistry.registerComponent('Appname', () => App);

使用

wdyr 支持多种配置项:github.com/welldone-so…

其中比较常用的包括:

  • trackAllPureComponents: 如果设置为 true,则会自动检测所有 PureComponent 以及 React.memo 包裹的组件
  • trackExtraHooks: 用于检测 hooks 的变化是否会引起 re-render
// wdyr.js
if (__DEV__) {
  const React = require('react');
  const ReactRedux = require('react-redux');
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
      trackAllPureComponents: true,
      trackExtraHooks: [
          [ReactRedux, 'useSelector']
      ],
  });
}

如果你的组件并不是 PureComponent 或 React.memo,那么只要直接设置组件的 whyDidYouRender 属性为 true,wdyr 就会对你的组件进行检测。

const Comp = () => {...};
Comp.whyDidYouRender = true;

export default Comp;

使用效果

在配置完成之后,我们来看看实际使用效果:

我在自己的 RN 项目中引入了这个库,用于检测项目首页的组件渲染问题,首页的代码结构大致如下:

// Homepage.tsx

const Homepage = () => {
  // 一些 state
  const [state1, setState1] = useState();
  const [state2, setState2] = useState();
  
  // 一些 redux
  const state1 = useSelector(state1);
  const state2 = useSelector(state2);
  
  // ...
  
  return (
     {/* 首页头部组件 */}
     <Header />
     
     {/* 首页其他组件 */}
     <Comp1 ... />
     <Comp2 ... />
     <Comp3 ... />
  )
}

AppRegistry.registerComponent('Homepage', () => Homepage);

接下来,我要对 Header 组件进行渲染检测,由于不是 React.memo,所以需要手动设置 whyDidYouRender 属性为 true:

// Header.tsx

const Header = () => {
    // ...
    return <>{...}</>
}
Header.whyDidYouRender = true;

export default Header;

配置成功后,重新进行本地调试,可以看到输出了如下 render log:

image.png

渲染优化

通过分析前面产生的 render log,我们可以得到 Header 组件触发 re-render 的主要原因:

image.png

这句 log 的意思是 Header 组件由于 props 发生变化,导致触发了 re-render。而 props 发生变化的原因是父组件 Homepage 的 state / redux store 触发了更新。

大家看到这里可能会有疑问,我 Header 组件明明没有接收任何 props,为什么还会因为 props 更新而重新渲染呢?

请记住:JSX 中, <Header /> 只是 React.createElement('Header', {})的语法糖。 这意味着每当 Header 的父组件重新渲染时,Header 都会尝试使用新的 props 对象来重新渲染。

在 log 中 wdyr 也给出了优化建议,由于 Header 不接收任何 props,意味着父组件的更新并不影响 Header 的更新,因此可以使用 React.memo 将其包裹起来,减少重复渲染开销。

优化后的 Header 组件如下:

// Header.tsx
import React from 'react';

const Header = () => {
    // ...
    return <>{...}</>
}
Header.whyDidYouRender = true;

export default React.memo(Header);

优化后可以发现,Header 组件不会再出现 re-render 的情况了。

写在最后

why-did-you-render 的确是个检测 React / React Native 组件重复渲染的神器,它不仅能帮忙定位 re-render 问题,还会提供一些优化建议。

大家如果需要对项目进行一些渲染优化和性能优化,不妨尝试一下。

但千万记住,只能在本地调试中使用它!

BTW, React Native 项目在 Expo 上使用这个库可能会遇到一些问题,请参考这里的解决方案:github.com/welldone-so…