你真的了解setState()吗?
React 中 setState()详细解读
对于 setState() 相信伙伴们都用过,它是 React 官方推荐用来更新组件 state 的 API,但是对于 setState() 你真的了解吗?且待我慢慢详聊一番。
setState() 官方用法指南
语法1: setState(updater[, callback])
updater:函数类型,返回一个更新后的 state 中的状态对象,它会和 state 进行浅合并。
callback: 可选,回调函数。
语法2: setState(stateChange[, callback])
setState: 对象类型,会将传入的对象浅层合并到新的 state 中。
callback:可选,回调函数。
对于这两种形式,不同的是第一个参数选择问题,可以选择一个函数返回一个新的state对象,亦可以直接选择一个对象应用于状态更新,那么啥时候选择函数类型的参数,什么时候选择对象类型的呢?这里可以总结两句话:
当前状态更新无需依赖之前的state状态时,选择对象类型参数
当前更新状态依赖之前的状态时,选择函数类型参数
example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>setState详解</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class A extends React.Component {
state = {
count: 0
}
update1 = () => {
this.setState({count: this.state.count+1})
}
update2 = () => {
this.setState(state => ({
count: state.count+1
}))
}
update3 = () => {
this.setState({
count: 8
})
}
render () {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button>
</div>
)
}
}
ReactDOM.render(
<A/>,
document.getElementById('app')
)
</script>
</body>
</html>

这个例子中,我们通过点击按钮测试1或测试2来改变组件 A 的 count 状态值,因为每次修改状态都是在原先基础上加 1, 所以在setState 中适合选择函数类型参数,即 update2 写法推荐。
点击 测试3 按钮会直接将count 值修改为 固定值 8,这无需依赖上一次count状态值,所以在setState 中适合选择对象类型参数,即 update3 写法推荐。
setState() 更新状态一定是异步的吗?
我们知道setState() 会触发组件render() 函数,重新渲染组件将更新后的内容显示在视图上,那么在 setState() 之后我们立马就能获取到最新的state值吗?
这里涉及到一个 setState() 是异步更新还是同步更新的问题?
结论:
在React相关的回调函数中setState() 是异步更新
不在React 相关的回调中setState() 是同步更新
React 相关的回调包括:组件的生命周期钩子,React 组件事件监听回调。
React不相关的回调包括常见的:setTimeout(), Promise()等。
我们还是可以拿之前的按钮点击实例来测试。
example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>setState详解</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class A extends React.Component {
state = {
count: 0
}
update1 = () => {
this.setState({count: this.state.count+1})
console.log(this.state.count)
}
update2 = () => {
setTimeout(() => {
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
})
}
update3 = () => {
Promise.resolve().then(value => {
this.setState({
count: 8
})
console.log(this.state.count)
})
}
componentWillMount () {
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
}
render () {
console.log('render()', this.state.count)
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button>
</div>
)
}
}
ReactDOM.render(
<A/>,
document.getElementById('app')
)
</script>
</body>
</html>
我们在 React 事件监听回调 update1 和 组件生命周期 componentWillMount() 钩子里面分别在setState()之后打印最新的 state 值,发现打印出来的还是修改之前的state,但是页面已经更新为最新状态,看图:


采用同样的方法我们可以观察在 update2 的setTimeout() 和 update3 的 Promise() 回调中,setState() 后打印的是最新的state值,而且这个打印会在setState() 触发组件重新render() 之后。经过测试,恰好验证了我们的结论是正确的,在React 相关的回调中setState()是异步更新状态,在不相关的回调中 setState() 是同步更新状态。
setState() 异步更新状态时,如何获取最新的状态值?
这个问题其实是针对当setState() 异步更新状态之后,怎么立马获取到最新的状态值,也就是上面例子我们说的在update1() 和componentWillMount()中怎么打印出最新的state值。
答案其实非常简单,也就是我们说到的setState()传参的第二个callback() 参数。setState() 的第二个回调会在更新状态之后,组件重新render() 之后调用,也就是这里面我们可以获取到最新的状态值。
代码:
...
update1 = () => {
this.setState({count: this.state.count+1}, () => {
console.log(this.state.count)
})
}
componentWillMount () {
this.setState(state => ({
count: state.count+1
}), () => {
console.log(this.state.count)
})
}
这样,我们同样可以在update1 和 componentWillMount() 中 打印出最新的state值。
遇到重复多次调用setState(),React如何处理?
这里我们讨论的前提当然是setState() 异步更新状态时候,因为同步更新,我们调用几次 setState(),就会触发几次 render钩子,当然也会实时分别打印出更新后的状态值。
结论:
这里分两种情况讨论:
当setState() 传对象类型参数,React会合并重复多次的调用setState(),触发一次render。
当setState() 传函数类型参数,React会依次多次的调用setState(),触发一次render。
可以看到,我们多次重复调用setState(),不管是传参是何种类型。React都只会调用一次 render,重新渲染组件。
我们可以同样以按钮点击实例来测试我们结论。
example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>setState详解</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
class A extends React.Component {
state = {
count: 0
}
update1 = () => {
// this.setState({count: this.state.count+1}, () => {
// console.log(this.state.count)
// })
// this.setState({count: this.state.count+1}, () => {
// console.log(this.state.count)
// })
// this.setState({count: this.state.count+1}, () => {
// console.log(this.state.count)
// })
this.setState((state) => ({
count: state.count+1
}), () => {
console.log(this.state.count)
})
this.setState((state) => ({
count: state.count+1
}), () => {
console.log(this.state.count)
})
this.setState((state) => ({
count: state.count+1
}), () => {
console.log(this.state.count)
})
}
update2 = () => {
setTimeout(() => {
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
})
}
update3 = () => {
Promise.resolve().then(value => {
this.setState({
count: 8
})
console.log(this.state.count)
})
}
componentWillMount () {
this.setState(state => ({
count: state.count+1
}))
console.log(this.state.count)
}
render () {
console.log('render()', this.state.count)
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.update1} style={{marginRight: 15}}>测试1</button><button style={{marginRight: 15}} onClick={this.update2}>测试2</button><button onClick={this.update3}>测试3</button>
</div>
)
}
}
ReactDOM.render(
<A/>,
document.getElementById('app')
)
</script>
</body>
</html>
当点击测试按钮2,因为setState() 是同步更新状态,可以发现组件进行了多次render调用,分别依次打印出更新后的状态值,这个很简单。

我们点击测试按钮1,分别对传给setState()参数不同进行了测试,发现当传参是对象类型时候,React会合并重复setState()调用,也就是只更新一次state状态,传函数类型参数时候,则分别进行了计算更新。


无论以哪种方式传参重复调用 setState() ,React 都只会进行一次render 调用,这也是性能优化的一部分,防止多次重复渲染带来的性能问题。
其实官网推荐我们使用setState()时候,第一个参数传函数类型参数,因为函数参数中接收的 state 和 props 都保证为最新。
你真的了解setState()吗?的更多相关文章
- 聊聊数据库(MySql)连接吧,你真的清楚吗?
前言 说到数据库连接,这个大家都很熟悉了.但是熟悉一般来自于下面三种情况 * 刚开始学编程的时候,老师就说用完的数据库连接一定要关闭,不然会有严重的后果. * 编程一段时间后,大家都说要用连接池来优化 ...
- react 入门教程 阮一峰老师真的是榜样
- 转自阮一峰老师博客 React 入门实例教程 作者: 阮一峰 日期: 2015年3月31日 现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Nati ...
- 理解setState
近来在学习react源码, 最初是直接从入口一行一行的看, 结果跟着调用的函数跳转来跳去头都晕了. 后来决定带着一个目的去看源码, 每次看只研究一个东西. 一开始最想了解的就是充满魔性的setStat ...
- [React技术内幕] setState的秘密
对于大多数的React开发者,setState可能是最常用的API之一.React作为View层,通过改变data从而引发UI的更新.React不像Vue这种MVVM库,直接修改data并不能视图的改 ...
- 聊一聊Flutter的setState()
Flutter 里面包含两种widget 一种可变的,一种不可变的: 在可变的widget中可以使用 setstate(){} 函数. 官方也给出了例子: _onClick(){ setState() ...
- 你真的会用react hooks?看看eslint警告吧!(如何发请求、提升代码性能等问题)
前言 看过几个react hooks 的项目,控制台上几百条警告,大多是语法不规范,react hooks 使用有风险,也有项目直接没开eslint.当然,这些项目肯定跑起来了,因为react自身或者 ...
- App你真的需要么
随着智能手机.移动路联网的普及,APP火的一塌糊涂,APP应用可谓五花八门,街上经常看到各种推广:扫码安装送东西,送优惠券.仿佛一夜之间一个企业没有自己的APP就跟不上时代了. 有时我在想:APP,你 ...
- [C#] C# 知识回顾 - 你真的懂异常(Exception)吗?
你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...
- 你真的会玩SQL吗?之逻辑查询处理阶段
你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...
随机推荐
- 洛谷P2319 [HNOI2006]超级英雄 题解
题目链接: https://www.luogu.org/problemnew/show/P2319 分析 每错,这是一道海南不对是河南呀呀呀错了是湖南的省选题. 但是还是可以作为二分图第二题来练手的, ...
- cocos creator 小游戏区域截图功能实现
截图是游戏中非常常见的一个功能,在cocos中可以通过摄像机和 RenderTexture 可以快速实现一个截图功能,具体API可参考:https://docs.cocos.com/creator/m ...
- mysql 不同版本下 group by 组内排序的差异
最近发现网上找的 group by 组内排序语句在不同的mysql版本中结果不一样. 建表语句: SET FOREIGN_KEY_CHECKS=0; -- ---------------- ...
- HDU 多校 第三场 Find the answer
这题是原来cf上的一道原题,不过对于有一些数据范围修改了,不过还是很好想的 题意:给定一个长度为N的数组,对于数组中的每个位置,满足当前和小于M所需要去掉的最小代价 分析:对于当前是否需要进行去掉一些 ...
- TestNG独立运行的几种方法.md
目录 通过main函数调用testng.xml文件,然后打成可执行jar包执行 1 假设我们已经写好了testng.xml,现在我们需要写一个主类和main函数用来调用testng.xml 2 把项目 ...
- 05-k8s调度器、预选策略、优选函数
目录 k8s调度器.预选策略.优选函数 节点选择过程 调度器 预选策略 优选函数 高级调度设置机制 node选择器/node亲和调度 pod亲和性 污点调度 Taints 与 Tolerations ...
- Vue 报错 listen EADDRINUSE :::8080
今天在重启vue项目的时候,发现报了错, listen EADDRINUSE :::8080错误提示 原因:因为另一个项目占用了8080端口,我直接在命令行npm run dev第二个项目,就给出了这 ...
- Linux学习笔记05之网络基础知识
一.OSI参考模型:适用于所有网络,现有模型,后有协议 1.应用层:应用程序.用户接口 2.表示层:编码转换.压缩.解压.加密等 3.会话层:建立.维护.拆除会话 4.传输层规定了应用程序的的接口 协 ...
- DesignPattern系列__01SingletonResponsibility
单一职责原则 单一职责原则:一个类应该只有一个原因引起改变,即一个类应该只负责一个业务逻辑. 问题由来:类T负责t1, t2两个职责,当因为t1j对类T修改的时候,可能导致类T出现问题而影响职责t2. ...
- DesignPattern系列__03依赖倒置原则
依赖倒置原则(Dependence Inversion Priiciple,DIP) 介绍 High level modules should not depend upon low level mo ...