react基础(九)

88 阅读3分钟

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

前言

大家好呀,我是L同学。在上篇文章中,我们学习了setState的基本使用以及setState的异步更新。在这篇文章中,我们还继续介绍setState的更新,包括同步更新,数据合并等。

setState同步

在上篇文章中,我们讲到了setState的异步。那么setState一定是异步的吗?

我们来测试下。这个案例是刚开始显示文本hello world,点击按钮后通过setState更改数据,改为hello react。我们把setState放到setTimeout中进行更新。

import React, { Component } from 'react'

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

    this.state = {
      message: 'hello world'
    }
  }

  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

  changeText() {
    setTimeout(() => {
      this.setState({
        message: 'hello react'
      })
      console.log(this.state.message);
    }, 0)
  }
}

我们可以看到刚开始显示的是hello world。

image.png 当我点击按钮后,控制台打印出了hello react,页面也进行了更新。

image.png

我们再看另外一种情况,原生DOM事件。

同一个案例,增加一个按钮,同样地点击按钮2更新数据。

  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
        <button id='btn'>改变文本2</button>
      </div>
    )
  }

  componentDidMount() {
    document.getElementById('btn').addEventListener('click', (e) => {
      this.setState({
        message: 'hello react'
      })
      console.log(this.state.message);
    })
  }

我们可以看到刚开始数据是hello world。点击按钮后获取到的数据是hello react,页面上数据也进行了更新。

image.png

我们来总结一下。setState不一定是异步的。分两种情况:

  • 在组件生命周期或者react合成事件(如react中的onClick事件)中,setState是异步的。
  • 在setTimeout或者原生DOM事件中,setState是同步的。

setState数据的合并

import React, {Component} from 'react'

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

    this.state = {
      message: 'hello world',
      name: 'haha'
    }
  }

  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
        <h2>{this.state.name}</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
      </div>
    )
  }

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

我们点击按钮,改变了message的数据,但是不会对name产生影响的。

image.png

image.png

react源码中有对原对象和新对象进行合并的,做了以下操作。

Object.assign({}, this.state, {message: 'hello react'})

setState本身的合并

import React, {Component} from "react";

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

    this.state = {
      counter: 0
    }
  }

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

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

我们猜测下点击按钮counter会加3次变成3吗?

image.png 我们看到点击按钮一次,counter加了1,并没有加3。这是因为setState本身进行了合并。

image.png 那如果我们需要setState合并时进行累加,点击按钮counter变为3,该怎么做呢?

  increment() {
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
  }

我们可以看到点击按钮一次,counter变为了3。

image.png

组件嵌套的render调用

接下来我们做这样一个案例,各个组件之间的嵌套关系如图所示。在App组件中有一个+1按钮,可以让counter数据加1,并且会重新调用render函数。

image.png

import React, { 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 Component {
  render() {
    console.log('Main render函数被调用');
    return (
      <div>
        <Banner/>
        <ProductList/>
      </div>
    )
  }
}

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

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

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log('App 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
    })
  }

}

我们可以看到各个组件都被调用了,创建组件的时候就会调用render函数。

image.png 我们清空控制台,然后点击+1按钮。此时会重新调用App的render函数。而当App的render函数被调用时,所有的子组件的render函数都会被重新调用。 image.png 我们只更新了App组件中的数据,没有改变其他组件,但是它们都会被重新render,进行diff算法,这会造成性能低下。它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法。

此时我们就可以通过shouldComponentUpdate方法来控制render的调用。shouldComponentUpdate方法我们在下篇文章中详细介绍。