react的setstate是同步还是异步?

Admin 2021-10-21 群英技术资讯 418 次浏览

    这篇文章主要给大家分享的是关于react的setstate的内容,对于setstate是同步还是异步的问题,一些朋友可能不是理解,对此我们通过示例来了解一下,感兴趣的朋友就继续往下看吧。

    setState是同步还是异步

    自定义合成事件和react钩子函数中异步更新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的打印结果是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中同步更新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;
    

    setState相关源码

    如下代码均来自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改动还挺大,暂时先写到这里,如果有新的发现会补充到这里。

    总结

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

        现在大家对于“setstate是同步还是异步”应该都有所了解了,上述示例有一定的借鉴价值,有需要的朋友可以参考,希望对大家了解react的setstate有帮助,想要了解更多大家可以关注群英网络其它相关文章。

    文本转载自脚本之家 

    群英智防CDN,智能加速解决方案
    标签: react的setstate

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

    猜你喜欢

    成为群英会员,开启智能安全云计算之旅

    立即注册
    专业资深工程师驻守
    7X24小时快速响应
    一站式无忧技术支持
    免费备案服务
    免费拨打  400-678-4567
    免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
    在线客服
    微信公众号
    返回顶部
    返回顶部 返回顶部
    在线客服
    在线客服