詳解react setState
setState是同步還是異步
自定義合成事件和react鉤子函數(shù)中異步更新state
以在自定義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的打印結(jié)果是1,頁面展示的是2。通過現(xiàn)象看,三次setState只是最后一次setState生效了,前兩次都setState無效果。因為假如把第一次setState改為+3,count打印結(jié)果為1,展示結(jié)果為2,沒有發(fā)生變化。而且沒有同步獲得count的結(jié)果。
此時,我們可以調(diào)整代碼,通過setState的第二個參數(shù),來獲得更新后的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的回調(diào)函數(shù)中,打印結(jié)果分別是。
1
1: 2
2: 2
3: 2
首先,最后一行直接打印1。然后,在setState的回調(diào)中,打印出的結(jié)果都是最新更新的2。雖然前兩次setState未生效,但是它們第二個參數(shù)中還是會打印出2。
此時將setState的第一個參數(shù)換成函數(shù),通過函數(shù)的第一個參數(shù)可以獲得更新前的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;
此時,打印出的結(jié)果為1,但是頁面展示出來的count為4??梢园l(fā)現(xiàn),如果setState以傳參的方式去更新state,幾次setState并不會只更新最后一次,而是幾次更新state都會生效。
接下來看下第二個函數(shù)中打印的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的回調(diào)函數(shù)中,打印結(jié)果如下,可想而知,頁面的展示結(jié)果也為4
1
1: 4
2: 4
3: 4
將上邊代碼放入如componentDidMount中,輸出結(jié)果跟上邊一致。
因為,可以得知,在自定義合成事件和鉤子函數(shù)中,state的更新是異步的。
原生事件和setTimeout中同步更新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;
此時,打印出的結(jié)果如下:
1: 2
2: 3
3: 4
4
將setState第一個參數(shù)換為函數(shù):
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);
}
打印出的結(jié)果和上邊一致。
是不是有一種state完全可控的感覺,在setTimeout中,多次setState都會生效,而且在每一個setState的第二個參數(shù)中都可以得到更新后的state。
同樣地,在原生事件中輸出地結(jié)果和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;
setState相關(guān)源碼
如下代碼均來自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可以接收兩個參數(shù),第一個參數(shù)可以是object,function,和null,undefined,就不會拋出錯誤。執(zhí)行下邊的this.updater.enqueueSetState方法。全局查找enqueueSetState,找到兩組目錄下有這個變量。
首先是第一組目錄:
目錄 ./packages/react/src/ReactNoopUpdateQueue.js 第100行enqueueSetState方法,參數(shù)分別為this,初始化state,回調(diào),和字符串setState,this是指當(dāng)前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;
}
}
這段代碼相當(dāng)于給didWarnStateUpdateForUnmountedComponent對象中加入屬性,屬性的key為React 當(dāng)前要setState的組件.setState,如果當(dāng)前有這個屬性則返回;如果當(dāng)前沒這個屬性或者這個屬性值為false,則設(shè)置這個屬性的值為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 這個函數(shù)
目錄 ./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;
}
}
}
看到這里,發(fā)現(xiàn)這個方法是將此次更新的update加入到更新隊列中,而在這個版本中并沒有發(fā)現(xiàn)isBatchingUpdates這個屬性的出現(xiàn)。貌似React Fiber改動還挺大,暫時先寫到這里,如果有新的發(fā)現(xiàn)會補充到這里。
總結(jié)
- 自定義合成事件和react鉤子函數(shù)中異步更新state
- 原生事件和setTimeout中同步更新state
以上就是詳解react setState的詳細(xì)內(nèi)容,更多關(guān)于react setState的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react中useEffect函數(shù)的詳細(xì)用法(最新推薦)
useEffect是React中的一個Hook,用于在函數(shù)組件中處理副作用(如數(shù)據(jù)獲取、訂閱、手動更改 DOM 等),useEffect屬于組件的生命周期方法,下面通過本文給大家分享react中useEffect函數(shù)的詳細(xì)用法,感興趣的朋友跟隨小編一起看看吧2024-06-06
ReactNative支付密碼輸入框?qū)崿F(xiàn)詳解
這篇文章主要為大家介紹了ReactNative支付密碼輸入框?qū)崿F(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
React庫之react-beautiful-dnd介紹及其使用過程
在使用React構(gòu)建Web應(yīng)用程序時,拖拽功能是一項常見需求,為了方便實現(xiàn)拖拽功能,我們可以借助第三方庫react-beautiful-dnd,本文將介紹react-beautiful-dnd的基本概念,并結(jié)合實際的項目代碼一步步詳細(xì)介紹其使用過程,需要的朋友可以參考下2023-11-11
一文搞懂?React?18?中的?useTransition()?與?useDeferredValue()
這篇文章主要介紹了一文搞懂?React?18?中的?useTransition()與useDeferredValue(),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
React+TS+IntersectionObserver實現(xiàn)視頻懶加載和自動播放功能
通過本文的介紹,我們學(xué)習(xí)了如何使用 React + TypeScript 和 IntersectionObserver API 來實現(xiàn)一個視頻播放控制組件,該組件具有懶加載功能,只有在用戶滾動頁面且視頻進(jìn)入視口時才開始下載視頻資源,需要的朋友可以參考下2023-04-04

