setState是同步還是異步
自定義合成事件和react鉤子函數(shù)中異步更新state
以在自定義click事件中的setState為例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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; |
點(diǎn)擊一次,最終this.state.count的打印結(jié)果是1,頁(yè)面展示的是2。通過(guò)現(xiàn)象看,三次setState只是最后一次setState生效了,前兩次都setState無(wú)效果。因?yàn)榧偃绨训谝淮蝧etState改為+3,count打印結(jié)果為1,展示結(jié)果為2,沒(méi)有發(fā)生變化。而且沒(méi)有同步獲得count的結(jié)果。
此時(shí),我們可以調(diào)整代碼,通過(guò)setState的第二個(gè)參數(shù),來(lái)獲得更新后的state:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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; |
此時(shí),點(diǎn)擊一次,三個(gè)setState的回調(diào)函數(shù)中,打印結(jié)果分別是。
1
1: 2
2: 2
3: 2
首先,最后一行直接打印1。然后,在setState的回調(diào)中,打印出的結(jié)果都是最新更新的2。雖然前兩次setState未生效,但是它們第二個(gè)參數(shù)中還是會(huì)打印出2。
此時(shí)將setState的第一個(gè)參數(shù)換成函數(shù),通過(guò)函數(shù)的第一個(gè)參數(shù)可以獲得更新前的state。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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; |
此時(shí),打印出的結(jié)果為1,但是頁(yè)面展示出來(lái)的count為4。可以發(fā)現(xiàn),如果setState以傳參的方式去更新state,幾次setState并不會(huì)只更新最后一次,而是幾次更新state都會(huì)生效。
接下來(lái)看下第二個(gè)函數(shù)中打印的count是多少:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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; |
此時(shí),點(diǎn)擊一次,三個(gè)setState的回調(diào)函數(shù)中,打印結(jié)果如下,可想而知,頁(yè)面的展示結(jié)果也為4
1
1: 4
2: 4
3: 4
將上邊代碼放入如componentDidMount中,輸出結(jié)果跟上邊一致。
因?yàn)椋梢缘弥谧远x合成事件和鉤子函數(shù)中,state的更新是異步的。
原生事件和setTimeout中同步更新state
以在setTimeout中setState為例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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; |
此時(shí),打印出的結(jié)果如下:
1: 2
2: 3
3: 4
4
將setState第一個(gè)參數(shù)換為函數(shù):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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完全可控的感覺(jué),在setTimeout中,多次setState都會(huì)生效,而且在每一個(gè)setState的第二個(gè)參數(shù)中都可以得到更新后的state。
同樣地,在原生事件中輸出地結(jié)果和setTimeout中一致,也是同步的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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)源碼
如下代碼均來(lái)自react17.0.2版本
目錄 ./packages/react/src/ReactBaseClasses.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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可以接收兩個(gè)參數(shù),第一個(gè)參數(shù)可以是object,function,和null,undefined,就不會(huì)拋出錯(cuò)誤。執(zhí)行下邊的this.updater.enqueueSetState方法。全局查找enqueueSetState,找到兩組目錄下有這個(gè)變量。
首先是第一組目錄:
目錄 ./packages/react/src/ReactNoopUpdateQueue.js 第100行enqueueSetState方法,參數(shù)分別為this,初始化state,回調(diào),和字符串setState,this是指當(dāng)前React實(shí)例。
1
2
3
4
5
6
7
8
|
enqueueSetState: function ( publicInstance, partialState, callback, callerName, ) { warnNoop(publicInstance, 'setState' ); } |
接著看warnNoop方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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對(duì)象中加入屬性,屬性的key為React 當(dāng)前要setState的組件.setState,如果當(dāng)前有這個(gè)屬性則返回;如果當(dāng)前沒(méi)這個(gè)屬性或者這個(gè)屬性值為false,則設(shè)置這個(gè)屬性的值為true。
再去看另外一個(gè)目錄:
目錄 ./react-reconciler/src/ReactFiberClassComponent.new.js和ReactFiberClassComponent.old.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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 這個(gè)函數(shù)
目錄 ./react-reconciler/src/ReactUpdateQueue.new.js和ReactUpdateQueue.old.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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)這個(gè)方法是將此次更新的update加入到更新隊(duì)列中,而在這個(gè)版本中并沒(méi)有發(fā)現(xiàn)isBatchingUpdates這個(gè)屬性的出現(xiàn)。貌似React Fiber改動(dòng)還挺大,暫時(shí)先寫到這里,如果有新的發(fā)現(xiàn)會(huì)補(bǔ)充到這里。
總結(jié)
- 自定義合成事件和react鉤子函數(shù)中異步更新state
- 原生事件和setTimeout中同步更新state
以上就是詳解react setState的詳細(xì)內(nèi)容,更多關(guān)于react setState的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://juejin.cn/post/6948979480358551583