彻底搞清楚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 团 ...
随机推荐
- 剑指offer51:构建乘积数组B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1],不能使用除法
1 题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1] ...
- PHP和Memcached - 在PHP中的应用 - Memcached类介绍 - 封装自己的Memcached类库
1.Memcached类的介绍 详见PHP官方文档:点击访问. 2.封装自己的Memcached类库 <?php namespace Cache\Lib; class MemCache { /* ...
- 前端通过js获取手机型号
###前段通过js获取手机型号 需求: 用户登录后记录当前的手机型号并记录 插件: mobile-detect.js插件地址 mobile-device-js插件地址 使用步骤: 获取UA信息-> ...
- cv2.VideoWriter()指定写入视频帧编码格式
帧速率 fps 和 帧大小,通过VideoCapture类的get()函数得到. 编码参数:cv2.VideoWriter_fourcc('I','4','2','0')---未压缩的YUV颜色编码, ...
- SAS学习笔记29 logistic回归
变量筛选 当对多个自变量建立logistic回归模型时,并不是每一个自变量对模型都有贡献.通常我们希望所建立的模型将具有统计学意义的自变量都包含在内,而将没有统计学意义的自变量排除在外,即进行变量筛选 ...
- Mybatis-Plus myBatis的增强工具
1. Mybatis-Plus简介 1.1. 什么是Mybatis-Plus MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为 ...
- sqlserver 聚集索引 非聚集索引
聚集索引是一种对磁盘上实际数据重新组织以按指定的一列或者多列值排序.像我们用到的汉语字典,就是一个聚集索引.换句话说就是聚集索引会改变数据库表中数据的存放顺序.非聚集索引不会重新组织表中的数据,而是对 ...
- Vue路由嵌套
Vue路由嵌套 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- nginx和PHP之间的通信
如果程序员a和B在windows上开发代码,它们可以被分离到不同的服务器,因为nginx和PHP之间的通信是基于TCP fastcgi协议的我们可以在程序员的windows pc上安装nginx,使用 ...
- 一步一步教你实现iOS音频频谱动画(一)
如果你想先看看最终效果再决定看不看文章 -> bilibili 示例代码下载 第二篇:一步一步教你实现iOS音频频谱动画(二) 基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱 ...