前言

组件是构成React视图的一个基本单元。有些组件会有自己本地的状态(state), 有些组件依赖传入的参数(props)。当用户的操作而发生改变时,组件就会重新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。有些无用的重复的渲染可能在你不知不觉中进行着,当组件越来越大时,它们的存在会大大降低我们应用的性能。
本此将介绍React16.6加入的专门用来优化函数组件(Functional Component)性能的方法:React.memo

无用的渲染

我们来看一个类组件例子

  1. //子组件
  2. import React from 'react';
  3. class TestC extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. }
  7. componentDidUpdate(prevProps, prevState) {
  8. console.log('componentDidUpdate')
  9. }
  10. render() {
  11. return (
  12. <div>
  13. { this.state.count }
  14. </div>
  15. );
  16. }
  17. }
  18. export default TestC;
  19. //父组件
  20. import Test from './components/test'
  21. import React from 'react'
  22. class App extends React.Component {
  23. constructor() {
  24. super()
  25. this.state = {
  26. count: 1,
  27. }
  28. this.updateBaseClass = this.updateBaseClass.bind(this)
  29. }
  30. //点击按钮设置传入组件的值为2
  31. updateBaseClass(){
  32. this.setState({
  33. count:2
  34. })
  35. }
  36. render() {
  37. return (
  38. <div>
  39. <Test count={this.state.count}></Test>
  40. <button onClick={this.updateBaseClass}>update</button>
  41. </div>
  42. );
  43. }
  44. }
  45. export default App;

当我们点击Click按钮时,count的值被设置为2。这时候屏幕的数字将会由1变成2,组件重新渲染了,没有问题。当我们再次点击该按钮时,count的值还是2,这时候其实我们主观上并不想让TestC组件重新渲染,事实是每次点击他都会重新渲染。
image.png

类组件解决方案

pureComponent

React在v15.5的时候引入了Pure Component组件。React在进行组件更新时,如果发现这个组件是一个PureComponent,它会将组件现在的state和props和其下一个state和props进行浅比较,如果它们的值没有变化,就不会进行更新。要想让你的组件成为Pure Component,只需要extends React.PureComponent即可。

  1. //子组件
  2. import React from 'react';
  3. class TestC extends React.PureComponent {
  4. constructor(props) {
  5. super(props);
  6. }
  7. componentDidUpdate(prevProps, prevState) {
  8. console.log('componentDidUpdate')
  9. }
  10. render() {
  11. return (
  12. <div>
  13. { this.state.count }
  14. </div>
  15. );
  16. }
  17. }
  18. export default TestC;

函数组件

自React v16.8推出React hooks版本后,函数式组件也成为我们日常开发的选择之一。对于函数组件,我们不能像在类组件中使用shouldComponentUpdate等生命函数去控制函数组件的重渲染,更不能继承React.PureComponent。
我们先将类组件写成函数组件

  1. import React from 'react'
  2. const TestC = (props) => {
  3. console.log('Rendering TestC :', props)
  4. return (
  5. <span>
  6. { props.count }
  7. </span>
  8. )
  9. }
  10. export default TestC

在count值变与不变的情况,TestC都会去重新渲染,下面我们用 React.memo() 就改造一下他。

  1. import React from 'react'
  2. const TestC = (props) => {
  3. console.log('Rendering TestC :', props)
  4. return (
  5. <span>
  6. { props.count }
  7. </span>
  8. )
  9. }
  10. export default React.memo(TestC);

React.memo会返回一个纯化(purified)的组件 MemoFuncComponent,这个组件将会在JSX标记中渲染出来。当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染。

引用类型的变化导致React组件重新渲染

引用类型

“引用类型”是一个 JavaScript 程序员必须烂熟于心的概念,如果无法分辨这个概念,会无意中写出很多损耗性能的代码。有几种我们常见的引用类型,分别是对象(object)、数组(array)和函数(function)。

  1. //对象
  2. { name: 'a' } === { name: 'a' }; // false
  3. { name: 'a' } == { name: 'a' }; // false
  4. //数组
  5. [1, 2] === [1, 2]; // false
  6. [1, 2] == [1, 2]; // false
  7. //函数
  8. const funcA = () => "hello";
  9. const funcB = () => "hello";
  10. funcA === funcB; // false
  11. funcA == funcB; // false

来看一段代码

  1. //子组件
  2. import React from 'react'
  3. let TestC = (props) => {
  4. console.log('Rendering Test2 :', props)
  5. return (
  6. <span>
  7. { props.params[0].baseClass }
  8. </span>
  9. )
  10. }
  11. function areEqual(prevProps, nextProps) {
  12. console.log(prevProps, nextProps,prevProps==nextProps)
  13. return false
  14. }
  15. export default React.memo(TestC);
  16. //父组件
  17. import Test from './components/test'
  18. import Test2 from './components/test2'
  19. import React from 'react'
  20. class App extends React.Component {
  21. constructor() {
  22. super()
  23. this.state = {
  24. //查看 基础类型和引用类型 传入组件的更新情况
  25. baseClass: 1,
  26. objClass: [
  27. { baseClass: 1 }
  28. ]
  29. }
  30. this.updateBaseClass = this.updateBaseClass.bind(this)
  31. this.updateObjClass = this.updateObjClass.bind(this)
  32. }
  33. updateBaseClass(){
  34. this.setState({
  35. baseClass:2
  36. })
  37. }
  38. updateObjClass(){
  39. const a = [...this.state.objClass]
  40. a[0].baseClass=2
  41. this.setState({
  42. objClass:a
  43. })
  44. }
  45. render() {
  46. return (
  47. <div>
  48. <div style={{marginBottom:'20px'}}>
  49. <h3 style={{margin:0,display:'inline'}}>展示值:</h3><Test params={this.state.baseClass}></Test>
  50. <button onClick={this.updateBaseClass}>updateBaseClass</button>
  51. </div>
  52. <div>
  53. <h3 style={{margin:0,display:'inline'}}>展示值:</h3><Test2 params={this.state.objClass}></Test2>
  54. <button onClick={this.updateObjClass}>updateObjClass</button>
  55. </div>
  56. </div>
  57. );
  58. }
  59. }
  60. export default App;