首页前端开发JavaScript详解react setState

详解react setState

时间2024-02-01 09:06:02发布访客分类JavaScript浏览815
导读:收集整理的这篇文章主要介绍了详解react setState,觉得挺不错的,现在分享给大家,也给大家做个参考。 目录setState是同步还是异步自定义合成事件和react钩子函数中异步...
收集整理的这篇文章主要介绍了详解react setState,觉得挺不错的,现在分享给大家,也给大家做个参考。
目录
  • setState是同步还是异步
    • 自定义合成事件和react钩子函数中异步更新state
    • 原生事件和setTimeout中同步更新state
  • 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 enqueueUpdateState>
          (  fiber: Fiber,  update: UpdateState>
      ,  lane: Lane,) {
            const updateQueue = fiber.updateQueue;
        if (updateQueue === null) {
              // Only occurs if the fiber has been unmounted.    return;
        }
            const sharedQueue: SharedQueueState>
           = (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的资料请关注其它相关文章!

      您可能感兴趣的文章:
      • 深入理解React State 原理
      • 浅析React 对state的理解
      • 详解react中的state的简写方式
      • 深入研究React中setState源码
      • 深入掌握 react的 setState的工作机制
      • React 组件中的state和setState()你知道多少

      声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

      react

      若转载请注明出处: 详解react setState
      本文地址: https://pptw.com/jishu/595198.html
      vue element后台鉴权流程分析 react diff算法源码解析

      游客 回复需填写必要信息