react基础(十)

164 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

前言

大家好呀,我是L同学。在上篇文章中,我们讲到了只更新了App组件中的数据,没有改变其他组件,但是它们都会被重新render,进行diff算法,这会造成性能低下。它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法。在这篇文章中,我们将学习到以上问题的解决方法。

shouldComponentUpdate

我们可以通过shouldComponentUpdate方法来控制render的调用。现在我们来学习下shouldComponentUpdate的使用场景。

import React, {Component} from 'react'

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      counter: 0,
      message: 'hello world'
    }
  }

  render() {
    console.log('App render函数被调用');
    return (
      <div>
        <h2>当前计数:{this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeText() {
    this.setState({
      message: 'hello react'
    })
  }
}

我们可以看到页面上显示了counter和两个按钮。

image.png 当我们点击改变文本按钮的时候,message的值发生了改变,所以render监听到了state的改变,就会重新调用render方法。

image.png 在jsx中并没有依赖这个message,那么message的改变不应该引起重新render。这时我们可以使用shouldComponentUpdate这个生命周期方法。

shouldComponentUpdate这个生命周期方法接收两个参数,并且需要有返回值。

这个方法有两个参数。第一个参数是nextProps,修改之后,最新的props属性。第二个参数是nextState修改之后,最新的state属性。

这个方法返回值是一个Boolean类型。返回值是true,就需要调用render方法。返回值是false,就不需要调用render方法。默认返回的是true,也就是只要state发生改变,就会调用render方法。

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.counter !== nextState.counter) {
      return true
    }
    return false
  }

当我们点击+1按钮时,会重新render。

image.png 当我们点击改变文本按钮时,不会重新render。

image.png

如果我们所有的类都实现shouldComponentUpdate生命周期方法的话,会增加非常多的工作量。另一种方法,我们可以让class继承自PureComponent。

import React, {PureComponent, Component} from 'react'

function Header() {
  console.log('Header被调用');
  return <h2>我是Header组件</h2> 
}

class Banner extends Component {
  render() {
    console.log('Banner render函数被调用');
    return <h3>我是Banner组件</h3>
  }
}

function ProductList() {
  console.log('ProductList被调用');
  return (
    <ul>
      <li>商品列表1</li>
      <li>商品列表2</li>
      <li>商品列表3</li>
      <li>商品列表4</li>
      <li>商品列表5</li>
    </ul>
  )
}

class Main extends PureComponent {
  render() {
    console.log('Main render函数被调用');
    return (
      <div>
        <Banner/>
        <ProductList/>
      </div>
    )
  }
}

function Footer() {
  console.log('Footer被调用');
  return <h2>我是Footer组件</h2> 
}

export default class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
    <div>
      <h2>当前计数:{this.state.counter}</h2>
      <button onClick={e => this.increment()}>+1</button>
      <Header/>
      <Main/>
      <Footer/>
    </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

可以看到当点击+1按钮时,只有Header组件和Footer组件被重新渲染了。

image.png 对于类组件我们可以使用shouldComponentUpdate生命周期方法,还可以让class继承PureComponent。那么对于Header组件、Footer组件这种函数式组件该怎么解决呢?

我们需要使用一个高阶组件memo。将这些函数式组件都通过memo函数进行一层包裹。

首先要从react中导入memo。

import React, {PureComponent, Component, memo} from 'react'

然后使用memo函数将这些函数式组件进行包裹,返回的是新的组件。

const MemoHeader = memo(function Header() {
  console.log('Header被调用');
  return <h2>我是Header组件</h2> 
})
const MemoFooter = memo(function Footer() {
  console.log('Footer被调用');
  return <h2>我是Footer组件</h2> 
})

最后App组件的render函数调用的是新的组件。

  render() {
    return (
    <div>
      <h2>当前计数:{this.state.counter}</h2>
      <button onClick={e => this.increment()}>+1</button>
      <MemoHeader/>
      <Main/>
      <MemoFooter/>
    </div>
    )
  }

我们可以看到点击+1按钮后,没有重新调用其他组件。

image.png