抛出问题


class Example extends Component {
contructor () {
super()
this.state = {
value: 0,
index: 0
}
} componentDidMount () {
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第一次输出
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第二次输出
setTimeout(() => {
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第三次输出
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第四次输出
}, 0);
this.refs.button.addEventListener('click', this.click)
} click = () => {
this.setState({value: this.state.index + 1})
this.setState({value: this.state.index + 1})
} render () {
return (
<div><span>value: {this.state.value}index: {this.props.index}</span>
<button ref="button" onClick={this.click}>点击</button>
</div>
)
}
}
  • 这四次输出,按常理来说分别是: 1,2,3,4。但是,实际输出为: 0, 0, 2, 3

setState的注意点

  1. setState不会立刻改变React组件中state的值(即setState是异步更新)

    • setState通过一个队列机制实现state更新;
    • 当执行setState时,会将需要更新的state合并后放入状态队列,而不会立即更新,队列可以高效的批量更新state;
    • 通过this.state直接修改的值,state不会放入状态队列,当下次调用setState并对状态队列进行合并时,会忽略之前直接被修改的state.
  2. setState通过引发一次组件的更新过程来引发重新绘制

    • 此处重绘指的就是引起React的更新生命周期函数4个函数:
    • shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)
    • componentWillUpdate(被调用时this.state没有更新)
    • render(被调用时this.state得到更新)
    • componentDidUpdate
  3. 多个相邻的state的修改可能会合并到一起一次执行

this.setState({name: 'Pororo'})
this.setState({age: 20})
  • 等同于

this.setState({name: 'Pororo',age: 20})
  • 上面两块代码的效果是一样的。如果每次调用都引发一次生命周期更新,那性能就会消耗很大了。所以,React会将多个this.setState产生的修改放进一个队列里,等差不多的时候就会引发一次生命周期更新。

问题分析

  • 对于前两次setState:

this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第一次输出
this.setState({value: this.state.val + 1});
console.log(this.state.value); // 第二次输出
  • 由于setState不会立即改变React组件中state的值,所以两次setState中this.state.value都是同一个值0,故而,这两次输出都是0。因而value只被加1。
  • 既然这样,那么是不是可以直接操作this.state呢?比如:this.state.value=this.state.value+1;
  • 这样的确可以修改this.state.value的状态但是却不可以引发重复渲染。
  • 所以,就必须通过React设定的setState函数去改变this.state,从而引发重新渲染。
  • setTimeout里面的两次setState:

setTimeout(() => {
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第三次输出
this.setState({value: this.state.value + 1})
console.log(this.state.value) // 第四次输出
}, 0);
  • 这两次this.state的值同步更新了;
  • 同步更新:是由React引发的事件处理(比如:onClick引发的事件处理),调用setState会异步更新this.state;
  • 异步更新:除此之外的setState调用会同步执行this.setState。 “除此之外”指的是:绕过React通过addEventListener直接添加的事件处理函数和setTimeout/setInterval产生的异步调用。
  • this.setState更新机制图解:

  • 每次setState产生新的state会依次被存入一个队列,然后会根据isBathingUpdates变量判断是直接更新this.state还是放进dirtyComponent里回头再说。
  • isBatchingUpdates默认是false,也就表示setState会同步更新this.state。
  • 但是,当React在调用事件处理函数之前就会调用batchedUpdates,这个函数会把isBatchingUpdates修改为true,造成的后果就是由React控制的事件处理过程setState不会同步更新this.state。

同步更新(函数式setState)

  1. 如果this.setState的参数不是一个对象而是一个函数时,这个函数会接收到两个参数,第一个是当前的state值,第二个是当前的props,这个函数应该返回一个对象,这个对象代表想要对this.state的更改;
  2. 换句话说,之前你想给this.setState传递什么对象参数,在这种函数里就返回什么对象。不过,计算这个对象的方法有些改变,不再依赖于this.state,而是依赖于输入参数state。

function increment(state, props) {
return {count: state.count + 1};
} function incrementMultiple() {
this.setState(increment);
this.setState(increment);
this.setState(increment);
}
  • 假如当前this.state.count的值是0,第一次调用this.setState(increment),传给increment的state参数是0,第二调用时,state参数是1,第三次调用是,参数是2,最终incrementMultiple让this.state.count变成了3。
  • 对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。

要注意的是,在increment函数被调用时,this.state并没有被改变,依然,要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变。

同步异步setState的用法混合


function incrementMultiple() {
this.setState(increment);
this.setState(increment);
this.setState({count: this.state.count + 1});
this.setState(increment);
}
  • 在几个函数式setState调用中插入一个传统式setState调用,最后得到的结果是让this.state.count增加了2,而不是增加4。
  • 这是因为React会依次合并所有setState产生的效果,虽然前两个函数式setState调用产生的效果是count加2,但是中间出现一个传统式setState调用,一下子强行把积攒的效果清空,用count加1取代。
  • 所以,传统式setState与函数式setState一定不要混用。

总结自:掘金(不洗碗工作室)

原文地址:https://segmentfault.com/a/1190000014990454

深入React技术栈之setState详解的更多相关文章

  1. ELK技术栈之-Logstash详解

    ELK技术栈之-Logstash详解   前言 在第九章节中,我们已经安装好Logstash组件了,并且启动实例测试它的数据输入和输出,但是用的是最简单的控制台标准输入和标准输出,那这节我们就来深入的 ...

  2. 应用编排服务之ELK技术栈示例模板详解

    日志对互联网应用的运维尤为重要,它可以帮助我们了解服务的运行状态.了解数据流量来源甚至可以帮助我们分析用户的行为等.当进行故障排查时,我们希望能够快速的进行日志查询和过滤,以便精准的定位并解决问题. ...

  3. React源码 commit阶段详解

    转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...

  4. 重谈react优势——react技术栈回顾

    react刚刚推出的时候,讲react优势搜索结果是几十页. 现在,react已经慢慢退火,该用用react技术栈的已经使用上,填过多少坑,加过多少班,血泪控诉也不下千文. 今天,再谈一遍react优 ...

  5. Docker 基础技术之 Linux cgroups 详解

    PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...

  6. C/C++堆、栈及静态数据区详解

    转自:https://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html  做略微修改 C/C++堆.栈及静态数据区详解   本文介绍C ...

  7. php缓存技术——memcache常用函数详解

    php缓存技术——memcache常用函数详解 2016-04-07 aileen PHP编程 Memcache函数库是在PECL(PHP Extension Community Library)中, ...

  8. react技术栈实践(2)

    本文来自网易云社区 作者:汪洋 这时候还没完,又有两个问题引出来了. 按照上面的配置,第三方库 antd 竟然也被编译了,导致样式失败. react中,一旦包裹了子组件,子组件没办法直接使用 styl ...

  9. react第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制)

    第五单元(事件系统-原生事件-react中的合成事件-详解事件的冒泡和捕获机制) 课程目标 深入理解和掌握事件的冒泡及捕获机制 理解react中的合成事件的本质 在react组件中合理的使用原生事件 ...

随机推荐

  1. bzoj 2016: [Usaco2010]Chocolate Eating【二分+贪心】

    二分答案,贪心判断,洛谷上要开long long #include<iostream> #include<cstdio> using namespace std; const ...

  2. [POI2008]BLO-Blockade 【无向图tarjan/鸽点】By cellur925

    题目传送门 lyd无向图tarjan的例题. 一句话题意(不得不佩服lyd老师的高度概括能力):在一张无向连通图上,求出每个点被破坏(去掉与这个点相关的所有边,不去掉这个点)后,无向图中使i,j不连通 ...

  3. python爬虫爬取腾讯招聘信息 (静态爬虫)

    环境: windows7,python3.4 代码:(亲测可正常执行) import requests from bs4 import BeautifulSoup from math import c ...

  4. Unix\Linux | 总结笔记 | vi编辑器

    0 目录   新建文件夹   常用的命令    三种模式及切换    vi编辑器的启动选项及后续相关操作  其他--替换文本  其他--写入另一个文件     1  新建文件夹  vi [目录名/文件 ...

  5. 【数据结构(C语言版)系列二】 栈

    栈和队列是两种重要的线性结构.从数据结构角度看,栈和队列也是线性表,但它们是操作受限的线性表,因此,可称为限定性的数据结构.但从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据类型. 栈的定 ...

  6. SolrCloud索引富文本数据

    solrconfig配置文件: schema配置文件: 执行目录: /opt/solr-5.5.4/server/scripts/cloud-scripts -- 下载配置文件 ./zkcli.sh ...

  7. C#内存映射文件学习[转]

    原文链接 内存映射文件是由一个文件到进程地址空间的映射. C#提供了允许应用程序把文件映射到一个进程的函(MemoryMappedFile.CreateOrOpen).内存映射文件与虚拟内存有些类似, ...

  8. Linux oraenv Tips

    Linux for the Oracle DBA -Customizing the Oracle User's Environment There are many ways to customize ...

  9. 基于CentOS6.5或Ubuntu14.04下Suricata里搭配安装 ELK (elasticsearch, logstash, kibana)(图文详解)

    前期博客 基于CentOS6.5下Suricata(一款高性能的网络IDS.IPS和网络安全监控引擎)的搭建(图文详解)(博主推荐) 基于Ubuntu14.04下Suricata(一款高性能的网络ID ...

  10. form表单ajaxSubmit提交并验证

    html: <form class="register-form" action="{:U('')}" method="post"&g ...