彻底搞清楚setState
2024-09-01 21:15:26
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>`
}
}
最新文章
- Aix/Linux下自动备份oracle数据库
- IDL数组计算
- beta2阶段组员分数分配
- 【转】CentOS yum安装和卸载软件的使用方法
- oracle trunc 函数处理日期格式
- Python几个算法实现
- hdu1501Zipper(记忆化搜索)
- Unity3D 关于运动模型
- 安卓模拟器还是";genymotion";最靠谱.
- WEB架构师成长之路之三-架构师都要懂哪些知识
- Phonegap 版本minSdkVersion为8的时候的自动更新与升级
- dojo加载树错误
- The Swift Programming Language-官方教程精译Swift(2)基础知识
- WPF - 监听判断键盘组合键的按下
- Week 1 # A A + B Problem II
- wampserver 的Apache启动错误提示:The requested URL / was not found on this server.
- 计算机基础之Windows10操作系统安装U盘制作
- 【POJ1952】逢低吸纳 dp+计数
- git 分支branch
- 一次jenkins的错误
热门文章
- C++Primer 5th Chap9 Sequential Container
- python--接口自动化测试(接口状态)
- SAS学习笔记38 SAS Comments注释语句
- ASP.NET Core分布式项目-2.oauth密码模式identity server4实现
- C++新特性---智能指针
- 威联通212 http 在密码正确的情况下无法登录问题解决
- (五)Struts之Action类基础(二)
- (一)Maven基础及第一个Maven工程
- (一)SpringBoot之简介和安装插件以及HelloWorld第一个程序
- controller 层 date 类型的参数,SpringBoot自动转换 dateformat