彻底搞清楚setState
setState最常见的问题是,是异步的还是同步的?
setState在React.Component中的函数,是同步函数。但是我们调用的时候,不同的传参和不同的调用位置都会导致不同的结果。
从页面看有时候是同步的,有时候是异步的。
PS: 如果在componentDidMount触发,和用按钮事件触发是一样的。
1)调用函数中直接调用,调用完之后立即打印,结果不变,表现是异步
this.state = {number: 0}
add = () => {
this.setState({
number: this.state.number + 1}, () => console.log(this.state.number) //
)
// console.log(this.state.number); -- 0
} // 页面显示1
后台逻辑是,事件触发-->开启批量更新模式-->执行事件方法add-->状态存入队列[{number: 1}]-->方法结束(this.state.number=0)-->关闭批量更新模式
-->更新状态(number-1)-->更新组件(render)-->componentDidUpdate-->setState.callback(number-1)
2)批量调用setState会触发批量更新
如第一个参数是对象,状态会出现覆盖;第一个参数是函数,不会被覆盖,会累计。
state = {number: 0, name: 'lyra'}
add2 = () => {
this.setState((state) => ({number: state.number + 1}));
this.setState((state) => ({name: state.name + '-good'}));
this.setState((state) => ({number: state.number + 1}));
this.setState((state) => ({name: state.name + '-clever'}));
}// 执行完结果显示{number:2, name: lyra-good-clever}
add3 = () => {
this.setState({number: this.state.number + 1});
this.setState({name: this.state.name + '-good'});
this.setState({number: this.state.number + 1});
this.setState({name: this.state.name + '-clever'});
} // 执行完结果显示{number: 1, name: lyra-clever}
后台逻辑同上,会依次存入状态队列。但是更新状态的方式是:
this.queue.pendingStates.forEach(particalState => Object.assign(this.state, particalState));
如果传入对象,存储状态的时候直接存储。
if (typeof particalState === 'object') {
this.pendingStates.push(particalState);
}
如果传入的是方法,会先进行处理,每个状态,都是以前一个状态为基础
if (typeof particalState === 'function') {// 传入的是(state) => {}
this.setStateFuncs.push(particalState);
this.setStateFuncs.forEach(callback => {
const newParticalState = callback(this.component.state);
this.pendingStates.push(newParticalState);
Object.assign(this.component.state, newParticalState);
});
this.setStateFuncs.length = 0;// 注意清空!!
}
3)在异步任务回调函数中表现同步,且不批量更新
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0,
name: 'lyra',
}
}
componentDidMount() {
this.add();
}
add = () => {
this.setState({number: this.state.number + 1}, () => console.log('callback-3=',this.state));
this.setState({name: this.state.name + '-good'}, () => console.log('callback-4=',this.state));
this.setState({number: this.state.number + 1}, () => console.log('callback-5=',this.state));
this.setState({name: this.state.name + '-clever'}, () => console.log('callback-6=',this.state));
console.log('==1==',this.state);
setTimeout(() => {
console.log("==7===",this.state);
this.setState({ // 非批量处理,立即执行,触发render
number: this.state.number + 1,
name: this.state.name + '-bravo'
}, () => console.log('callback-8=',this.state))
console.log('==9==',this.state);
this.setState({
number: this.state.number + 1,
name: this.state.name + '-congratulations'
}, () => console.log('callback-10=',this.state))
console.log('==11==',this.state);
})
console.log("==2==",this.state);
}
render() {
console.log('render')
return (
<h1>Hello, world!</h1>
)
}
}
运行结果如下
==1=={number: 0, name: 'lyra'}
==2=={number: 0, name: 'lyra'}
render
callback-3={number:1, name: 'lyra-clever}
callback-4={number:1, name: 'lyra-clever}
callback-5={number:1, name: 'lyra-clever}
callback-6={number:1, name: 'lyra-clever}
==7==={number: 1, name: 'lyra-clever'}
render
callback-8={number: 2, name: 'lyra-clever-bravo'}
==9=={number: 2, name: 'lyra-clever-bravo'}
render
callback-10={number: 3, name: 'lyra-clever-bravo-congratulations'}
==11=={number: 3, name: 'lyra-clever-bravo-congratulations'}
后台逻辑: 事件触发-->开启批量更新模式-->执行事件方法主线程(不包含setTimeout宏任务)-->前四个状态存入队列{number: 1}-->主线程结束-->
关闭批量更新模式-->批量更新状态(this.state.number = 1)并渲染(DidUpdate)-->进入SetTImout-->setState-->非批量更新状态立即更新状态并渲染(DidUpdate)-->callback->
-->下一个setState-->......
PS:原生JS模拟setState实现
html
<!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>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./component.js"></script>
<script src='./counter.js'></script>
<script src="./stateFunc.js"></script>
<script>
let container = document.getElementById('root');
counterIns = new Counter({name: 'Lyra'}); //实例传属性
counterIns1 = new SetFuncCounter({name: 'Lyra1'}); //实例传属性
counterIns.mount(root); // ReactDOM.render(,window.root)
counterIns1.mount(root);
</script>
</body>
</html>
component.js
/*
* @Author: LyraLee
* @Date: 2019-11-18 23:49:56
* @LastEditTime: 2019-11-18 23:57:49
* @Description:
*/
// 用于批量更新的全局状态和方法
const batchingStragety = {
isBatchingUpdates: false, // 默认false,表面默认直接更新
dirtyComponents: [], // 状态和页面展示不同的组件叫脏组件;应该去重一下
batchUpdates() {
this.dirtyComponents.forEach(dirtyComponent => {
dirtyComponent.updateComponent();
});
this.dirtyComponents.length = 0;
}
} // 队列用于存储更新的状态,数据先入先出,有先后顺序
class QueueUpdate{
constructor(component) {
this.pendingStates = []; // 队列存值
this.setStateFuncs = []; // 传参是函数,存入该队列
this.component = component;
}
// 插入队列
addState(particalState) {
// particalState有两种形式,function或者object;
if (typeof particalState === 'object') {
this.pendingStates.push(particalState);
}
if (typeof particalState === 'function') {// 传入的是(state) => {}
this.setStateFuncs.push(particalState);
this.setStateFuncs.forEach(callback => {
const newParticalState = callback(this.component.state);
this.pendingStates.push(newParticalState);
Object.assign(this.component.state, newParticalState);
});
this.setStateFuncs.length = 0;// 注意清空!!
}
/*
然后判断isBatchingUpdating 是否是true,
如果是true,推入dirtyComponents,
*/
// 如果是false, 遍历所有的dirtyComponents,调用各自的updateComponent()
if (!batchingStragety.isBatchingUpdates) {
this.component.updateComponent();
return;
}
batchingStragety.dirtyComponents.push(this.component);
}
}
class Component {
constructor(props) {
this.props = props;
this.queue = new QueueUpdate(this); // 实例化一个队列,用于存储newState的值。
}
setState(particalState, callback) { // 传入需要更新的状态
/** this.setState(new State)->将newState存入队列 */
this.queue.addState(particalState);
// callbackcomponentDidMount前存入队列,
// 在componentDidMount后依次执行
}
updateComponent() { // 更新组件,在该方法中完成componentDidMount
this.queue.pendingStates.forEach(particalState => {
Object.assign(this.state, particalState)
});
this.queue.pendingStates.length = 0;
let oldElement = this.topElement;
let newElement = this.renderElement();
oldElement.parentNode.replaceChild(newElement, oldElement);
}
renderElement() { // 模拟ReactDOM.render(element, container)
let tempParent = document.createElement('div');
tempParent.innerHTML = this.render(); // 将模版字符串转为真实dom
this.topElement = tempParent.children[0]; // 使用属性,可以在this作用域暂存dom元素
this.topElement.component = this;
return this.topElement;
}
mount(container) {
container.appendChild(this.renderElement())
}
} // React中事件委托给全局document
window.trigger = function(event, method) {
const { component } = event.target;
// React中每次触发事件时开启批量更新模式,都默认将isBatchingUpdates置为true,事件调用完成置为false。
// 事件触发的核心逻辑代码是方法调用,isBatchingUpdates的状态变化可以看作一个事务,包裹方法
// 所有组件用这个方法,各个组件方法名可能相同,所以需要在实例上调用方法
const anyMethod = component[method].bind(component);
transaction.perform(anyMethod);
}
// 事务,将任何方法(anyMethod)用wrapper包裹,然后通过Transcation的perform()方法执行anyMethod
class Transcation{
// 先执行所有wrapper的initialize方法,再执行anyMethod,最后执行所有wrapper的close方法。
constructor(wrappers) {
this.wrappers = wrappers;
}
perform(anyMethod) {
this.wrappers.forEach(wrapper => {wrapper.initialize()});
anyMethod();
this.wrappers.forEach(wrapper => {wrapper.close()});
}
}
const transaction = new Transcation([{ // 传入wrappers,是个数组!!
initialize() {// 开启批量更新
batchingStragety.isBatchingUpdates = true;
},
close() {
batchingStragety.isBatchingUpdates = false;
// 执行完后要把状态置为false, 调用更新函数
batchingStragety.batchUpdates();
}
}]);
counter.js
class Counter extends Component{
constructor(props) {
super(props);
this.state = {
number: 0,
name: 'lyra'
}
}
add() {
this.setState({number: this.state.number + 1});
this.setState({name: this.state.name + '-good'});
this.setState({number: this.state.number + 1});
this.setState({name: this.state.name + '-clever'});
console.log('1==',this.state); // {number: 0, name: 'lyra'}
setTimeout(() => {
console.log("==3===",this.state); // {number: 1, name: 'lyra-clever'}
this.setState({ // 非批量处理,立即执行
number: this.state.number + 1,
name: this.state.name + '-bravo'
}) // 同步
console.log('==4==',this.state); // {number: 2, name: 'lyra-clever-bravo'}
this.setState({
number: this.state.number + 1,
name: this.state.name + '-congratulations'
})
console.log('==5==',this.state); // {number: 3, name: 'lyra-clever-bravo-congratulations'}
})
console.log("==2==",this.state); // {number: 0, name: 'lyra'}
}
render() {// 模拟JSX中虚拟DOM js中不能写html,只能写字符串模版,模版字符串只有一个顶级标签
return `<button id="counter" onclick="trigger(event, 'add')">
${this.state.name}
--------------<br/>
${this.state.number}
</button>`
}
}
彻底搞清楚setState的更多相关文章
- React源码解析:setState
先来几个例子热热身: ......... constructor(props){ super(props); this.state = { index: 0 } } componentDidMount ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...
- React16源码解读:开篇带你搞懂几个面试考点
引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...
- 搞定ReentrantReadWriteLock 几道小小数学题就够了
| 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...
- 搞个这样的APP要多久?
这是一个“如有雷同,纯属巧合”的故事,外加一些废话,大家请勿对号入座.开始了…… 我有些尴尬地拿着水杯,正对面坐着来访的王总,他是在别处打拼的人,这几年据说收获颇丰,见移动互联网如火如荼,自然也想着要 ...
- 【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】
说17号发超简单的教程就17号,qq核审通过后就封装了这个,现在放出来~~ 这个是我封装的一个开源项目:https://github.com/dunitian/LoTQQLogin ————————— ...
- 对百度WebUploader开源上传控件的二次封装,精简前端代码(两句代码搞定上传)
前言 首先声明一下,我这个是对WebUploader开源上传控件的二次封装,底层还是WebUploader实现的,只是为了更简洁的使用他而已. 下面先介绍一下WebUploader 简介: WebUp ...
- 彻底搞懂Javascript的“==”
本文转载自:@manxisuo的<通过一张简单的图,让你彻底地.永久地搞懂JS的==运算>. 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容让人犯错,从而 ...
- 在 Linux 中使用搜狗拼音输入法以及搞定 Flash 和支付宝
在 Ubuntu 中安装搜狗输入法 在 Ubuntu Kylin 系统中,默认安装搜狗拼音输入法,但是在原生 Ubuntu 系统中则不是.这可以理解,毕竟搜狗输入法的 Linux 版有 Kylin 团 ...
随机推荐
- vuex 理解
为什么要用vuex?页面由多个视图组成,用户操作会引视图的状态变化. 多个视图依赖于同一状态(例如:菜单导航) 来自不同视图的行为需要变更同一状态(例如:评论弹幕) vuex 的作用 为vue.js开 ...
- IOS+H5页面自定义按钮无效
在IOS整合H5页面的时候,自定义的按钮失去效果,Android系统可以. 如图,确定和取消按钮在IOS系统无效. 解决办法是在两个按钮上加上一个style属性即可 <span class=&q ...
- Spring Boot Redis 分布式缓存的使用
一.pom 依赖 <!-- 分布式缓存 --> <dependency> <groupId>org.springframework.boot</groupId ...
- 【Transact-SQL】让人快遗忘的游标
原文:[Transact-SQL]让人快遗忘的游标 最初学SQL Server的时候,当学到游标的时候,突然有了一种亲切感,因为这种通过一个while循环,一条一条的处理数据的方式,很像学过的过程式语 ...
- Mysql对查询结果添加序列号
; as rownum,* from table1 :=和=的区别? "=",只有在set和update时才和:=一样,赋值的作用,其它都是等于的作用.鉴于此,用变量实现行号时,必 ...
- luogu题解 P3950部落冲突--树链剖分
题目链接 https://www.luogu.org/problemnew/show/P3950 分析 大佬都用LCT,我太弱只会树链剖分 一个很裸的维护边权树链剖分题.按照套路,对于一条边\(< ...
- Seaborn(一)之风格管理
Seaborn简介 seaborn同matplotlib一样,也是Python进行数据可视化分析的重要第三方包.但seaborn是在 matplotlib的基础上进行了更高级的API封装,使得作图更加 ...
- js中prototype与__proto__的关系详解
一.构造函数: 构造函数:通过new关键字可以用来创建特定类型的对象的函数.比如像Object和Array,两者属于内置的原生的构造函数,在运行时会自动的出现在执行环境中,可以直接使用.如下: var ...
- 解决ios中input兼容性问题
1.解决input输入框在iOS中有阴影问题 input{ -webkit-appearance: none; } 2.checkbox.raido在ios中阴影问题 单选: ...
- ASIHTTPRequest源码简单分析
1.前言 ASIHttprequest 是基于CFNetwork的,由于CFNetwork是比较底层的http库,功能比较少,因此,在ASIHttprequest中实现了http协议中比 ...