彻底搞清楚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 团 ...
随机推荐
- MongoDB操作-备份和恢复
Mongodb数据库操作-备份 恢复 导出 导入 mongodb数据备份和恢复主要分为二种:一种是针对库的mongodump和mongorestore,一种是针对库中表的mongoexport和mon ...
- 怎样手写实现 new 命令的功能
实现new命令的功能, 首先需要知道new命令都做了些什么: 第一步: 创建一个空对象, 作为实例对象的容器; 第二步: 将空对象的prototype指向构造函数的prototype; 第三步: 将空 ...
- 通过数组的某一个属性值进行排序(如id)
let arr = [ {id: 1, name: 'aaa'}, {id: 4, name: 'ddd'}, {id: 2, name: 'bbb'}, {id: 3, name: 'ccc'} ] ...
- sql根据一个表查询的数据作为条件查询另一个表
代码格式如下: ) 要注意的是:in后面的查询语句必须是查询一个字段跟前面的表相对应的.比如要根据订单号orderID,OpenBills 这个表就需要查询到orderID这个字段,BillConsu ...
- 在浏览器输入 URL 回车之后发生了什么
注意:本文的步骤是建立在,请求的是一个简单的 HTTP 请求,没有 HTTPS.HTTP2.最简单的 DNS.没有代理.并且服务器没有任何问题的基础上. 大致流程 URL 解析 DNS 查询 TCP ...
- 一段代码显示出电脑连过所有wifi的密码
1.打开运行 2.输入cmd后回车 3.输入如下代码 for /f "skip=9 tokens=1,2 delims=:" %i in ('netsh wlan show pro ...
- Exams(二分
题意:给你每天要考的科目,和每门科目需要复习多长时间,问最少需要几天才能完成所有的考试. 思路:二分答案,然后判断答案是否可行,这边需要进行贪心,即倒着往前推, 比如第i天,那么前面有i-1天是,可供 ...
- tp5 sum某个字段相加得到总数
方法 说明count 统计数量,参数是要统计的字段名(可选)max 获取最大值,参数是要统计的字段名(必须)min 获取最小值,参数是要统计的字段名(必须)avg 获取平均值,参数是要统计的字段名(必 ...
- Java多维数组定义以及常见异常
import java.lang.*; import java.util.*; public class Demo1 { public static void main(String args[]){ ...
- axios封装,使用拦截器统一处理接口
1.项目路径下,引入axios.qs依赖 npm install axios npm install qs 2.在项目的src路径下新建一个commJs文件夹,在commJs文件夹里新建aps.js和 ...