gpt4 book ai didi

详解react setState

转载 作者:qq735679552 更新时间:2022-09-28 22:32:09 24 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章详解react setState由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

setState是同步还是异步

自定义合成事件和react钩子函数中异步更新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;

点击一次,最终this.state.count的打印结果是1,页面展示的是2。通过现象看,三次setState只是最后一次setState生效了,前两次都setState无效果。因为假如把第一次setState改为+3,count打印结果为1,展示结果为2,没有发生变化。而且没有同步获得count的结果.

此时,我们可以调整代码,通过setState的第二个参数,来获得更新后的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;

此时,点击一次,三个setState的回调函数中,打印结果分别是.

1 1: 2 2: 2 3: 2 。

首先,最后一行直接打印1。然后,在setState的回调中,打印出的结果都是最新更新的2。虽然前两次setState未生效,但是它们第二个参数中还是会打印出2.

此时将setState的第一个参数换成函数,通过函数的第一个参数可以获得更新前的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;

此时,打印出的结果为1,但是页面展示出来的count为4。可以发现,如果setState以传参的方式去更新state,几次setState并不会只更新最后一次,而是几次更新state都会生效.

接下来看下第二个函数中打印的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;

此时,点击一次,三个setState的回调函数中,打印结果如下,可想而知,页面的展示结果也为4 。

1 1: 4 2: 4 3: 4 。

将上边代码放入如componentDidMount中,输出结果跟上边一致.

因为,可以得知,在自定义合成事件和钩子函数中,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;

此时,打印出的结果如下:

1: 2 2: 3 3: 4 4 。

将setState第一个参数换为函数:

?
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);
}

打印出的结果和上边一致.

是不是有一种state完全可控的感觉,在setTimeout中,多次setState都会生效,而且在每一个setState的第二个参数中都可以得到更新后的state.

同样地,在原生事件中输出地结果和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相关源码

如下代码均来自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可以接收两个参数,第一个参数可以是object,function,和null,undefined,就不会抛出错误。执行下边的this.updater.enqueueSetState方法。全局查找enqueueSetState,找到两组目录下有这个变量.

首先是第一组目录:

目录 ./packages/react/src/ReactNoopUpdateQueue.js 第100行enqueueSetState方法,参数分别为this,初始化state,回调,和字符串setState,this是指当前React实例.

?
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 ;
   }
}

这段代码相当于给didWarnStateUpdateForUnmountedComponent对象中加入属性,属性的key为React 当前要setState的组件.setState,如果当前有这个属性则返回;如果当前没这个属性或者这个属性值为false,则设置这个属性的值为true.

再去看另外一个目录:

目录 ./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 这个函数 。

目录 ./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 ;
     }
   }
}

看到这里,发现这个方法是将此次更新的update加入到更新队列中,而在这个版本中并没有发现isBatchingUpdates这个属性的出现。貌似React Fiber改动还挺大,暂时先写到这里,如果有新的发现会补充到这里.

总结

  • 自定义合成事件和react钩子函数中异步更新state
  • 原生事件和setTimeout中同步更新state

以上就是详解react setState的详细内容,更多关于react setState的资料请关注我其它相关文章! 。

原文链接:https://juejin.cn/post/6948979480358551583 。

最后此篇关于详解react setState的文章就讲到这里了,如果你想了解更多关于详解react setState的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com