react系列-React.memo

组件渲染

什么时候会触发组件渲染

当传入组件的 props 和组件内部 state 更新时会触发组件渲染。

当父组件发生渲染就会触发子组件的渲染,如果是 class 组件则可以使用 React.PureComponent 进行优化,如果是函数组件则可以使用 React.memo 进行优化,减少渲染次数。

值得阅读的文章

React父组件刷新导致子组件重新渲染问题

深入了解React中state和props的更新

避免React生命周期的那些坑坑洼洼

React.memo

参考链接:优化React.memo渲染判别机制及实战

与普通的 FC 进行比较

普通的 FC 和被 React.memo 包裹后的 FC 渲染次数对比:

function TestComponent(props) {
  console.log('我重新渲染了');
  return <div>函数组件:1</div>;
}

const MyComponent = React.memo(function MyComponent(props) {
  console.log('memo: 我重新渲染了');
  return <div>memo函数组件:2</div>;
});
console.log(MyComponent);
class LifeCycle extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      text: '这个子组件文本',
      message: '这个是子组件消息'
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的子组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的子组件消息'
    });
  };
  render() {
    console.log('render LifeCycle class component');
    return (
      <div>
        <button onClick={this.changeText}>
          修改子组件文本内容
        </button>
        <button onClick={this.changeMessage}>
          修改子组件消息
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <div className='textContent'>
          <TestComponent />
          <MyComponent />
        </div>
      </div>
    );
  }
}

class LifeCycelContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: '父组件文本',
      message: '父组件消息'
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的父组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的父组件消息'
    });
  };
  render() {
    return (
      <div>
        <button onClick={this.changeText}>
          修改父组件文本
        </button>
        <button onClick={this.changeMessage}>
          修改父组件消息
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <LifeCycle />
      </div>
    );
  }
}

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

复杂的 props 数据结构

如果 props 数据结构过于复杂,React.memo 只进行浅层比较,也会触发多次渲染的问题。

function TestComponent(props) {
  console.log('我重新渲染了');
  return <div>函数组件:1</div>;
}

const MyComponent = React.memo(function MyComponent(props) {
  console.log('memo: 我重新渲染了', props.prop);
  return <div>memo函数组件:2</div>;
});

class LifeCycle extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      text: '这个子组件文本',
      message: '这个是子组件消息',
      count: 1,
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的子组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的子组件消息'
    });
  };
  addCount = () => {
     this.setState({
       count: this.state.count + 1
     })
  };
  
  getMonthlyDisableDate = (date) => {
    return true;
  };
  
  getChildProps = () => {
    return [
      {
        fieldName: 'reportId',
        type: 'select',
        label: '月报名称',
        clearable: false,
        value: [],
      },
      {
        fieldName: 'reportId2',
        type: 'select2',
        label: '月报名称2',
        clearable: false,
        value: [],
        disable: this.getMonthlyDisableDate,
      },
      {
        fieldName: 'reportId2',
        type: 'select2',
        label: '月报名称2',
        clearable: false,
        value: [],
        noResultText: () => (
          <div>
            暂无数据,去添加
          </div>
        ),
      }
    ];
  }
  
  render() {
    console.log('render LifeCycle class component');
    return (
      <div>
        <button onClick={this.changeText}>
          修改子组件文本内容
        </button>
        <button onClick={this.changeMessage}>
          修改子组件消息
        </button>
        <button onClick={this.addCount}>
          +1
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <div>count: {this.state.count}</div>
        <div className='textContent'>
          <TestComponent />
          <MyComponent prop={this.getChildProps()} />
        </div>
      </div>
    );
  }
}

class LifeCycelContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: '父组件文本',
      message: '父组件消息'
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的父组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的父组件消息'
    });
  };
  render() {
    return (
      <div>
        <button onClick={this.changeText}>
          修改父组件文本
        </button>
        <button onClick={this.changeMessage}>
          修改父组件消息
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <LifeCycle />
      </div>
    );
  }
}

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

React.memo 会对每次传入的 props 做浅层比较。比较算法 shallowEqual

shallowEqual

对基础数据格式、只有一层的数组或对象可以进行比较,如果包含函数或其他复杂的数据类型,则返回结果则为 false。

参考链接:Object.is() - MDN

自定义设置比较函数

React.memo 有第二个参数,可以自定义深层比较函数。

使用自定义的对比函数,改写上面的 demo:

function deepCompareObject(prevValue, nextValue) {
  if (Object.is(prevValue, nextValue)) {
    return true;
  }
  if (typeof prevValue !== typeof nextValue) {
    return false;
  }
  if (['string', 'number', 'boolean', 'undefined'].includes(typeof prevValue)) {
    return false;
  }
  if (prevValue === null || nextValue === null) {
    return false;
  }
  if (typeof prevValue === 'function' && typeof nextValue === 'function') {
    return prevValue.toString() === nextValue.toString();
  }
  // object & array
  if (typeof prevValue === 'object') {
    const prevValueKeys = Object.keys(prevValue);
    const nextValueKeys = Object.keys(nextValue);
    if (prevValueKeys.length !== nextValueKeys.length) {
      return false;
    }
    for (let i = 0; i < prevValueKeys.length; i++) {
      if (!Object.prototype.hasOwnProperty.call(nextValue, prevValueKeys[i])) {
        return false;
      }
      const result = deepCompareObject(prevValue[prevValueKeys[i]], nextValue[prevValueKeys[i]]);
      if (!result) {
        return false;
      }
    }
    return true;
  }
  return false;
}

function TestComponent(props) {
  console.log('我重新渲染了');
  return <div>函数组件:1</div>;
}

const MyComponent = React.memo(function MyComponent(props) {
  console.log('memo: 我重新渲染了');
  return <div>memo函数组件:2</div>;
}, deepCompareObject);

class LifeCycle extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      text: '这个子组件文本',
      message: '这个是子组件消息',
      count: 1,
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的子组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的子组件消息'
    });
  };
  addCount = () => {
     this.setState({
       count: this.state.count + 1
     })
  };
  
  getMonthlyDisableDate = (date) => {
    return true;
  };
  
  getChildProps = () => {
    return [
      {
        fieldName: 'reportId',
        type: 'select',
        label: '月报名称',
        clearable: false,
        value: [],
      },
      {
        fieldName: 'reportId2',
        type: 'select2',
        label: '月报名称2',
        clearable: false,
        value: [],
        disable: this.getMonthlyDisableDate,
      },
      {
        fieldName: 'reportId2',
        type: 'select2',
        label: '月报名称2',
        clearable: false,
        value: [],
        noResultText: () => (
          <div>
            暂无数据,去添加
          </div>
        ),
      }
    ];
  }
  
  render() {
    console.log('render LifeCycle class component');
    return (
      <div>
        <button onClick={this.changeText}>
          修改子组件文本内容
        </button>
        <button onClick={this.changeMessage}>
          修改子组件消息
        </button>
        <button onClick={this.addCount}>
          +1
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <div>count: {this.state.count}</div>
        <div className='textContent'>
          <TestComponent />
          <MyComponent value={this.getChildProps()} />
        </div>
      </div>
    );
  }
}

class LifeCycelContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      text: '父组件文本',
      message: '父组件消息'
    };
  }
  changeText = () => {
    this.setState({
      text: '修改后的父组件文本'
    });
  };
  changeMessage = () => {
    this.setState({
      message: '修改后的父组件消息'
    });
  };
  render() {
    return (
      <div>
        <button onClick={this.changeText}>
          修改父组件文本
        </button>
        <button onClick={this.changeMessage}>
          修改父组件消息
        </button>
        <div>{this.state.text}</div>
        <div>{this.state.message}</div>
        <LifeCycle />
      </div>
    );
  }
}

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