React系列九:深刻理解setState

快来加入咱们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至咱们官网小和山的菜鸟们 ( xhs-rookies.com/ ) 进行学习,及时获取最新文章。前端

"Code tailor" ,若是您对咱们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与咱们取的联系,您也能够在微信上观看咱们的文章。每个建议或是赞同都是对咱们极大的鼓励!react

前言

这节咱们将介绍 ReactsetState ,但愿能够帮助你们真正理解 setStategit

本文会向你介绍如下内容:github

  • 如何使用 setState
  • 不能直接修改 State
  • setState()
  • setState 多是异步更新
  • setState 的合并

如何使用 setState

在介绍 setState 以前,咱们先来看一个 setState 的案例,了解一下是如何使用的。web

咱们来展现一个使用案例,当点击一个 改变文本 的按钮时,修改界面前显示的内容:服务器

默认展现

案例代码

import React, { Component } from 'react'

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

    this.state = {
      message: 'Hello React',
    }
  }

  render() {
    return (
      <div> <h2>{this.state.message}</h2> <button onClick={(e) => this.changeMessage()}>ChangeMessage</button> </div>
    )
  }

  changeMessage() {
    this.setState({
      message: 'Hello xhs,your message is changed.',
    })
  }
}
复制代码

state 的初始化是在类构造器中去设置的,而后若是想要更改 state ,那就是经过 setState 函数,该函数最主要的就是传入一个对象做为参数,而该对象就是你想要修改的值。上述代码中,当你点击 ChangeMessage 时,就会调用 setState 函数,而 setState 会调用 render 函数,页面就会被从新渲染。微信

点击按钮以后,从新渲染的效果:markdown

从新渲染以后

不能直接修改 State

将上面的 changeMessage 方法,改为下面的样子。dom

changeMessage() {
  this.state.message = "Hello xhs,your message is changed.";
}
复制代码

点击 ChangeMessage 以后,页面并无改变,可是打印 state 你会发现,state 中的 message 变了,可是页面不会被从新渲染。异步

构造函数,是咱们惟一能够给 this.state 赋值的地方,而为了能够从新渲染页面,那就只能经过 setState 函数来修改。

因此,咱们经过调用 setState 来修改数据:

  • 当咱们调用 setState 时,会从新执行 render 函数,根据最新的 State 来建立 ReactElement 对象
  • 再根据最新的 ReactElement 对象,对 DOM 进行修改;
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  })
}
复制代码

setState()

setState(updater, [callback])
复制代码

setState() 将对组件 state 的更改排入队列,并通知 React 须要使用更新后的 state 从新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式

setState() 视为 请求 而不是当即更新组件的命令。为了更好的感知性能,React 会延迟调用它,而后经过一次传递更新多个组件。React 并不会保证 state 的变动会当即生效。

setState() 并不老是当即更新组件。它会批量推迟更新。这使得在调用 setState() 后当即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式均可以保证在应用更新后触发。

参数一能够有两种形式

1. 接受对象类型

setState(stateChange[, callback])
复制代码

stateChange 会将传入的对象浅层合并到新的 state 中,例如 让 count +1

this.setState({ count: this.state.count + 1 })
复制代码

这种形式的 setState() 也是异步的,而且在同一周期内会对多个 setState 进行批处理。例如,若是在同一周期内屡次 count + 1,则至关于:

Object.assign(
  previousState, // 以前的状态
  {count: state.count + 1},
  {count: state.count + 1},
  ...
)
复制代码

后调用的 setState() 将覆盖同一周期内先调用 setState 的值,所以商品数仅增长一次。若是后续状态取决于当前状态,咱们建议使用 updater 函数 的形式代替:

this.setState((state) => {
  return { count: state.count + 1 }
})
复制代码

2. 接受函数参数

咱们在初始的 state 中并无一个 count 。可是如今咱们有一个需求:那就是添加一个 countstate 中,使用对象做为第一参数,你就会发现这样一个问题。

changeCount () {
	this.setState({
		count: 0
	}) // => this.state.count 是 undefined
	this.setState({
		count: this.state.count + 1
	}) // => undefined + 1 = NaN
	this.setState({
		count: this.state.count + 2
	}) // => NaN + 2 = NaN
}
复制代码

上面的代码的运行结果并不能达到咱们的预期,咱们但愿 count 运行结果是 3 ,但是最后获得的是 NaN。可是这种后续操做依赖前一个 setState 的结果的状况并不罕见。

这里就天然地引出了 setState 的第二种使用方式,能够接受一个函数做为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可使用该结果进行运算、操做,而后返回一个对象做为更新 state 的对象:

changeCount () {
	this.setState((prevState) => {
		return { count: 0 }
	})
	this.setState((prevState) => {
		return { count: prevState.count + 1 }
    // 上一个 setState 的返回是 count 为 0,这里须要执行 +1 因此当前返回 1
  })
	this.setState((prevState) => {
		return { count: prevState.count + 2 }
    // 上一个 setState 的返回是 count 为 1,这里须要执行 +2 因此当前返回 3
	})
  // 最后的结果是 this.state.count 为 3
}
复制代码

这样就能够达到上述的利用上一次 setState 结果进行运算的效果。

参数二为回调函数

setState() 的第二个参数为可选的回掉函数,它将在 setState 完成合并并从新渲染组件后执行。一般,咱们建议使用 componentDidUpdate() 来代替此方式。

咱们来给案例中的 changeMessage 函数添加两个打印方式。

changeMessage() {
  this.setState({
		message: "Hello xhs,your message is changed."
	},() => {
    console.log('callback result: ',this.state.message);
  })
  console.log('no callback result:',this.state.message);
}
复制代码

咱们来看看结果

在这里插入图片描述

从图片中咱们能够看出

  • setState 外部的时候,并不能够立马拿到咱们想要的结果
  • callback 中则返回的是修改以后的结果。

正由于不是立马拿到咱们想要的结果,因此这就是咱们接下来要讲的 setState 多是异步更新

这样咱们就能够知道 setState 的第二参数的做用,就是能够确保获得 state 已经修改以后的结果。也就是从新渲染以后执行的内容。

setState 多是异步更新

咱们来看下面的代码:

  • 最终打印结果是 Hello React
  • 可见 setState 是异步的操做,咱们并不能在执行完 setState 以后立马拿到最新的 state 的结果
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  })
  console.log(this.state.message); // Hello React
}
复制代码

为何 setState 设计为异步呢?

  • setState 设计为异步其实以前在 GitHub 上也有不少的讨论;
  • React 核心成员(Redux 的做者)Dan Abramov 也有对应的回复,能够参考一下;

咱们来对他的回答作一个简单的总结:

  • setState 设计为异步,能够显著的提高性能;

  • 若是每次调用 setState 都进行一次更新,那么意味着 render 函数会被频繁调用,界面从新渲染,这样效率是很低的;

注意: 最好的办法应该是获取到多个更新,以后进行批量更新;

  • 若是同步更新了 state ,可是尚未执行 render 函数,那么 stateprops 不能保持同步;

注意:stateprops 不能保持一致性,会在开发中产生不少的问题;

获取到更新后的值

  • setState 的第二参数,一个回调函数,这个回调函数会在更新后会执行;
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  }, () => {
    console.log(this.state.message); // Hello xhs,your message is changed.
  });
}
复制代码

固然,咱们也能够在生命周期函数:

componentDidUpdate(prevProps, provState, snapshot) {
  console.log(this.state.message);
}
复制代码

setState 必定是异步?

疑惑:setState必定是异步更新的吗?

验证一:在 setTimeout 中的更新:

changeText() {
  setTimeout(() => {
    this.setState({
      message: "你好啊,小和山"
    });
    console.log(this.state.message); // 你好啊,小和山
  }, 0);
}
复制代码

验证二:原生 DOM 事件:

componentDidMount() {
  const btnEl = document.getElementById("btn");
  btnEl.addEventListener('click', () => {
    this.setState({
      message: "你好啊,小和山"
    });
    console.log(this.state.message); // 你好啊,小和山
  })
}
复制代码

其实分红两种状况:

  • 在组件生命周期或 React 合成事件中,setState 是异步;
  • setTimeout 或者原生 dom 事件中,setState 是同步;

setState 的合并

数据的合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

state 定义一些数据

this.state = {
  name: 'xhs',
  message: 'Hello React',
  count: 0,
}
复制代码

经过 setState 去修改 state 中的 message ,是不会对 name 产生影响的

changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  });
}
复制代码

多个 setState 会被合并

好比咱们仍是有一个 count 属性,默认为 0,记录当前的数字:

  • 执行下面的操做以后,最后的答案是 1
  • 多个 state 进行了合并
increment() {
	this.setState({
		count: this.state.count + 1
	});

  this.setState({
    count: this.state.count + 1
  });

  this.setState({
    count: this.state.count + 1
  });
}
复制代码

如何能够作到,让 count 最终变成 3 呢?

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

  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  })

  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  })
}
复制代码

上面的代码就是用到了,setState 接收一个函数做为第一参数的状况,来解决这个问题,就能够很好的获得咱们期待的结果。

下节预告

本节咱们学习了 ReactSetState 其中的奥秘,在下一个章节咱们将继续学习 React 中受控组件和非受控组件的内容,敬请期待!

相关文章
相关标签/搜索