减少不必要的渲染是提高组件性能的重要一环。


函数事件

可从绑定this方面着手,举几个例子佐证。
jsx中不要定义函数、jsx中不要绑定this
一、直接绑定this

  1. class techCat extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. xxx: aaa
  6. }
  7. }
  8. handleClick() {
  9. this.setState({xxx: ccc})
  10. }
  11. render() {
  12. return (
  13. <div>
  14. <text>{this.state.xxx}</text>
  15. <button onClick={this.handleClick.bind(this)}>点我</button>
  16. </div>
  17. )
  18. }
  19. }
  20. 缺点: 每次render都会bind,性能不是太好;如果有多个元素都调用这个函数,则会重复bind
  21. 虽然实例中定义存储了函数,但是bind方法却会返回一个新的函数,同样加大了内存占用和垃圾回收的开销。
  22. 可以将函数直接定义为箭头函数,或者在constructor中使用bindthis指向。

二、constructor 手动bind this

  1. class techCat extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. xxx: aaa
  6. }
  7. this.handleClick = this.handleClick.bind(this)
  8. }
  9. handleClick() {
  10. this.setState({xxx: ccc})
  11. }
  12. render() {
  13. return (
  14. <div>
  15. <text>{this.state.xxx}</text>
  16. <button onClick={this.handleClick}>点我</button>
  17. </div>
  18. )
  19. }
  20. }
  21. 优点:相比于第一种性能更好,因为构造函数只执行一次,那么只会 bind 一次,
  22. 而且如果有多个元素都需要调用这个函数,也不需要重复 bind,基本上解决了第一种的两个缺点。

三、箭头函数类型

  1. class techCat extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. xxx: aaa
  6. }
  7. }
  8. handleClick() {
  9. this.setState({xxx: ccc})
  10. }
  11. render() {
  12. return (
  13. <div>
  14. <text>{this.state.xxx}</text>
  15. <button onClick={(e) => {this.handleClick(e)}}>点我</button>
  16. </div>
  17. )
  18. }
  19. }
  20. 缺点:每次render都会重复创建函数,性能差。
  21. render方法中定义的函数会在每次组件更新中重新定义,每次定义又都会重新申请一块内存,造成更多的内存占用,触发js的垃圾回收也会增大开销,严重影响性能。
  22. 应该将函数存在实例上,持久化方法和内存,在render中绑定或使用。

四、优化后的箭头函数型

  1. class techCat extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. xxx: aaa
  6. }
  7. }
  8. handleClick = () => {
  9. this.setState({xxx: ccc})
  10. }
  11. render() {
  12. return (
  13. <div>
  14. <text>{this.state.xxx}</text>
  15. <button onClick={this.handleClick}>点我</button>
  16. </div>
  17. )
  18. }
  19. }
  20. 优点:好看且性能好,没什么缺点

组件粒度细分(合理拆分组件)

  1. 通常的写法:
  2. class Page extends PureComponent () {
  3. constructor(props) {
  4. super(props)
  5. this.state = {
  6. a: 1,
  7. ...
  8. }
  9. }
  10. render() {
  11. const { a } = this.props.data;
  12. return <div>
  13. {...}
  14. </div>
  15. }
  16. }
  17. reactdiff比对是以组件为单位进行的,page也是一个大组件。
  18. 所有的数据都在一个页面,任何一个状态的变化会引起整个页面的刷新。
  19. 合理地拆分组件, 并且结合PureComponent定义组件,
  20. 可以减少页面无变化部分的render次数,同时diff比对的粒度更细。
  21. 优化的写法:
  22. class Page extends PureComponent () {
  23. constructor(props) {
  24. super(props)
  25. this.state = {
  26. a: 1,
  27. b: 2
  28. }
  29. }
  30. render() {
  31. const { a } = this.props.data;
  32. return <div>
  33. <Component1 a={a} />
  34. <Component2 b={b} />
  35. ...
  36. </div>
  37. }
  38. }

pureComponent 和 shouldComponentUpdate

在 React 类组件中,可以利用 shouldComponentUpdate 或者 PureComponent 来减少因父组件更新而触发子组件的 render。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。
pureComponent:
先考虑props传递过来的是基本数据类型还是引用类型,若是基本数据则可使用pureComponent,反之Component
pureComponet 通过对 props 和 state 的浅比较结果来实现 shouldComponentUpdate,当对象包含复杂的数据结构时,可能就不灵了,对象深层的数据已改变却没有触发 render

看下shallowEqual 是如何实现浅比较的

  1. function shallowEqual(objA: mixed, objB: mixed): boolean {
  2. // 首先对基本类型进行比较
  3. if (is(objA, objB)) {
  4. return true;
  5. }
  6. if (typeof objA !== 'object' || objA === null ||
  7. typeof objB !== 'object' || objB === null) {
  8. return false;
  9. }
  10. const keysA = Object.keys(objA);
  11. const keysB = Object.keys(objB);
  12. // 长度不相等直接返回false
  13. if (keysA.length !== keysB.length) {
  14. return false;
  15. }
  16. // key相等的情况下,再去循环比较
  17. for (let i = 0; i < keysA.length; i++) {
  18. if (
  19. !hasOwnProperty.call(objB, keysA[i]) ||
  20. !is(objA[keysA[i]], objB[keysA[i]])
  21. ) {
  22. return false;
  23. }
  24. }
  25. return true;
  26. }

使用 React.memo

React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo 只能用于函数组件。

  1. 基本用法
  2. import { memo } from 'react';
  3. function Button(props) {
  4. // Component code
  5. }
  6. export default memo(Button);
  7. 高级用法
  8. 默认情况下其只会对 props 做浅层对比,遇到层级比较深的复杂对象时,表示力不从心了。
  9. 对于特定的业务场景,可能需要类似 shouldComponentUpdate 这样的 API
  10. 这时通过 memo 的第二个参数来实现:
  11. function arePropsEqual(prevProps, nextProps) {
  12. // your code
  13. return prevProps === nextProps;
  14. }
  15. export default memo(Button, arePropsEqual);
  16. 注意:与 shouldComponentUpdate 不同的是:
  17. arePropsEqual 返回 true 时,不会触发 render,如果返回 false,则会。
  18. shouldComponentUpdate 刚好与其相反。

React.Fragment

React.Fragment 组件能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。

  1. render() {
  2. return (
  3. <React.Fragment>
  4. Some text.
  5. <h2>A heading</h2>
  6. </React.Fragment>
  7. );
  8. }