彻底搞清楚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 团 ...
随机推荐
- STL标准模板库之vector
目录 vector容器 1)什么是vector 2)如何定义 3)常用的Vector函数 1.容量函数 2.增加函数 3.删除函数 4.迭代器 5.访问函数 6.其他函数及操作 7.算法 STL提供了 ...
- python笔记007-函数
昨日简要: 1.文件操作: 1.1获得句柄: f = open(‘one.txt’,mode=’’,encoding=’utf-8’) f = open(‘../’) à返回上一层 f = open( ...
- zookeeper-伪分布式搭建
1. 下载解压 2. 在conf目录下,新建3个文件(3台服务器):zoo1.cfg.zoo2.cfg.zoo3.cfg zoo1.cfg: tickTime=2000dataDir=/tmp/zoo ...
- Apache Tomcat 安装与配置教程
JDK的安装与配置 1. 从官网下载JDK https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-213315 ...
- Django——关于项目开发遇到的一些小技巧
目录 头像图片 js获取网站信息 js获取前端信息 Dj获取刚写入的数据的信息 js跳转新链接 头像图片对象的显示 在做到根据登陆ID决定用户头像的时候,加载静态文件的{% static ‘xxxxx ...
- Java中编码问题
在开发过程中经常会遇到一会乱码问题,不是什么大问题,但是也挺烦人的,今天来将我们开发总结的经验记录下来,希望可以给大家一些帮助. 一些概念: 字符:人们使用的记号,抽象意义上的一个符号.比如:‘1’, ...
- NodeJs 的Module.export 和 export
NodeJs 的Module.export 和 export 是一样的. 但是Module.export ={....} 可以起效,.export ={....} 是失效的. 这里的export ...
- 转:让PIP源使用国内镜像,提升下载速度和安装成功率
转载,自己记录使用,原文http://www.cnblogs.com/microman/p/6107879.html 对于Python开发用户来讲,PIP安装软件包是家常便饭.但国外的源下载速度实在太 ...
- sql语句分页多种方式
sql语句分页多种方式ROW_NUMBER()OVER sql语句分页多种方式ROW_NUMBER()OVER 2009年12月04日 星期五 14:36 方式一 select top @pageSi ...
- 【转载】salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载
salesforce 零基础开发入门学习(一)Salesforce功能介绍,IDE配置以及资源下载 目前国内已经有很多公司做salesforce,但是国内相关的资料确是少之又少.上个月末跳槽去了新 ...