react的setstate是同步还是异步?
Admin 2021-10-21 群英技术资讯 1044 次浏览
这篇文章主要给大家分享的是关于react的setstate的内容,对于setstate是同步还是异步的问题,一些朋友可能不是理解,对此我们通过示例来了解一下,感兴趣的朋友就继续往下看吧。
以在自定义click事件中的setState为例
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
handleClick = () => {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
console.log(this.state.count);
}
render() {
return (
<div style={{ width: '100px', height: '100px', backgroundColor: "yellow" }}>
{this.state.count}
</div>
)
}
}
export default Test;
点击一次,最终this.state.count的打印结果是1,页面展示的是2。通过现象看,三次setState只是最后一次setState生效了,前两次都setState无效果。因为假如把第一次setState改为+3,count打印结果为1,展示结果为2,没有发生变化。而且没有同步获得count的结果。
此时,我们可以调整代码,通过setState的第二个参数,来获得更新后的state:
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
handleClick = () => {
this.setState({
count: this.state.count + 3
}, () => {
console.log('1', this.state.count)
});
this.setState({
count: this.state.count + 1
}, () => {
console.log('2', this.state.count);
});
this.setState({
count: this.state.count + 1
}, () => {
console.log('3', this.state.count);
});
console.log(this.state.count);
}
render() {
return (
<div style={{ width: '100px', height: '100px', backgroundColor: "yellow" }}>
{this.state.count}
</div>
)
}
}
export default Test;
此时,点击一次,三个setState的回调函数中,打印结果分别是。
1
1: 2
2: 2
3: 2
首先,最后一行直接打印1。然后,在setState的回调中,打印出的结果都是最新更新的2。虽然前两次setState未生效,但是它们第二个参数中还是会打印出2。
此时将setState的第一个参数换成函数,通过函数的第一个参数可以获得更新前的state。
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
handleClick = () => {
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
});
console.log(this.state.count);
}
render() {
return (
<div style={{ width: '100px', height: '100px', backgroundColor: "yellow" }}>
{this.state.count}
</div>
)
}
}
export default Test;
此时,打印出的结果为1,但是页面展示出来的count为4。可以发现,如果setState以传参的方式去更新state,几次setState并不会只更新最后一次,而是几次更新state都会生效。
接下来看下第二个函数中打印的count是多少:
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
handleClick = () => {
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('1', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('2', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('3', this.state.count);
});
console.log(this.state.count);
}
render() {
return (
<div style={{ width: '100px', height: '100px', backgroundColor: "yellow" }}>
{this.state.count}
</div>
)
}
}
export default Test;
此时,点击一次,三个setState的回调函数中,打印结果如下,可想而知,页面的展示结果也为4
1
1: 4
2: 4
3: 4
将上边代码放入如componentDidMount中,输出结果跟上边一致。
因为,可以得知,在自定义合成事件和钩子函数中,state的更新是异步的。
以在setTimeout中setState为例
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
count: this.state.count + 1
}, () => {
console.log('1:', this.state.count);
});
this.setState({
count: this.state.count + 1
}, () => {
console.log('2:', this.state.count);
});
this.setState({
count: this.state.count + 1
}, () => {
console.log('3:', this.state.count);
});
console.log(this.state.count);
}, 0);
}
render() {
return (
<div
style={{
width: '100px',
height: '100px',
backgroundColor: "yellow"
}}>
{this.state.count}
</div>
)
}
}
export default Test;
此时,打印出的结果如下:
1: 2
2: 3
3: 4
4
将setState第一个参数换为函数:
componentDidMount() {
setTimeout(() => {
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('1', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('2', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('3', this.state.count);
});
console.log(this.state.count);
}, 0);
}
打印出的结果和上边一致。
是不是有一种state完全可控的感觉,在setTimeout中,多次setState都会生效,而且在每一个setState的第二个参数中都可以得到更新后的state。
同样地,在原生事件中输出地结果和setTimeout中一致,也是同步的。
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
};
}
componentDidMount() {
document.body.addEventListener('click', this.handleClick, false);
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClick, false);
}
handleClick = () => {
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('1', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('2', this.state.count);
});
this.setState((prevState, props) => {
return { count: prevState.count + 1 }
}, () => {
console.log('3', this.state.count);
});
console.log(this.state.count);
}
render() {
return (
<div
style={{
width: '100px',
height: '100px',
backgroundColor: "yellow"
}}
>
{this.state.count}
</div>
)
}
}
export default Test;
如下代码均来自react17.0.2版本
目录 ./packages/react/src/ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
setState可以接收两个参数,第一个参数可以是object,function,和null,undefined,就不会抛出错误。执行下边的this.updater.enqueueSetState方法。全局查找enqueueSetState,找到两组目录下有这个变量。
首先是第一组目录:
目录 ./packages/react/src/ReactNoopUpdateQueue.js 第100行enqueueSetState方法,参数分别为this,初始化state,回调,和字符串setState,this是指当前React实例。
enqueueSetState: function(
publicInstance,
partialState,
callback,
callerName,
) {
warnNoop(publicInstance, 'setState');
}
接着看warnNoop方法:
const didWarnStateUpdateForUnmountedComponent = {};
function warnNoop(publicInstance, callerName) {
if (__DEV__) {
const constructor = publicInstance.constructor;
const componentName =
(constructor && (constructor.displayName || constructor.name)) ||
'ReactClass';
const warningKey = `${componentName}.${callerName}`;
if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
return;
}
console.error(
"Can't call %s on a component that is not yet mounted. " +
'This is a no-op, but it might indicate a bug in your application. ' +
'Instead, assign to `this.state` directly or define a `state = {};` ' +
'class property with the desired state in the %s component.',
callerName,
componentName,
);
didWarnStateUpdateForUnmountedComponent[warningKey] = true;
}
}
这段代码相当于给didWarnStateUpdateForUnmountedComponent对象中加入属性,属性的key为React 当前要setState的组件.setState,如果当前有这个属性则返回;如果当前没这个属性或者这个属性值为false,则设置这个属性的值为true。
再去看另外一个目录:
目录 ./react-reconciler/src/ReactFiberClassComponent.new.js和ReactFiberClassComponent.old.js
const classComponentUpdater = {
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update, lane);
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitions(root, fiber, lane);
}
if (__DEV__) {
if (enableDebugTracing) {
if (fiber.mode & DebugTracingMode) {
const name = getComponentNameFromFiber(fiber) || 'Unknown';
logStateUpdateScheduled(name, lane, payload);
}
}
}
if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
}
}
其中主要看 enqueueUpdate 这个函数
目录 ./react-reconciler/src/ReactUpdateQueue.new.js和ReactUpdateQueue.old.js
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
if (isInterleavedUpdate(fiber, lane)) {
const interleaved = sharedQueue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
update.next = update;
// At the end of the current render, this queue's interleaved updates will
// be transfered to the pending queue.
pushInterleavedQueue(sharedQueue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
sharedQueue.interleaved = update;
} else {
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
if (__DEV__) {
if (
currentlyProcessingQueue === sharedQueue &&
!didWarnUpdateInsideUpdate
) {
console.error(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
didWarnUpdateInsideUpdate = true;
}
}
}
看到这里,发现这个方法是将此次更新的update加入到更新队列中,而在这个版本中并没有发现isBatchingUpdates这个属性的出现。貌似React Fiber改动还挺大,暂时先写到这里,如果有新的发现会补充到这里。
现在大家对于“setstate是同步还是异步”应该都有所了解了,上述示例有一定的借鉴价值,有需要的朋友可以参考,希望对大家了解react的setstate有帮助,想要了解更多大家可以关注群英网络其它相关文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
本文实例为大家分享了js实现简单滑动解锁功能以及滑动拼图解锁的具体代码,供大家参考,具体内容如下简单实现滑动解锁,效果图是这样的
这篇文章主要给大家介绍了关于JavaScript实现的七种排序算法的相关资料,七种排序算法分别是:冒泡排序、选择排序、插入排序、希尔排序、堆排序、快速排序以及归并排序,需要的朋友可以参考下
作用域是指程序源代码中定义变量的区域,作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限,这篇文章主要给大家介绍了关于JavaScript静态作用域和动态作用域的相关资料,需要的朋友可以参考下
这篇文章主要为大家详细介绍了vue+elementui实现下拉表格多选和搜索功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
用for循环语句或filter()方法循环历数组,在每个循环中将一个数组元素分别去除2到sqrt(元素本身)。如果可以去除,则表明该组元素并非质数,而是质数。
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008