美文网首页
setState原理

setState原理

作者: 木中木 | 来源:发表于2019-11-09 21:45 被阅读0次
    • setState是异步更新的
      setState调用时,会执行enqueueSetState方法对当前要设置的state和_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。首先,我们看个例子,如下所示,如果你知道答案那么你对这个机制是了解的。
     componentDidMount() {
        console.log('App dit')
        this.setState({value: this.state.value + 1})
        console.log('value:', this.state.value)
        this.setState({value: this.state.value + 1})
        console.log('value:', this.state.value)
        setTimeout(() => {
          this.setState({value: this.state.value + 1})
          console.log('value:', this.state.value)
          this.setState({value: this.state.value + 1})
          console.log('value:', this.state.value)
        }, 0);
        
      }
    
    

    下面是张简单的机制流程图


    1-1.png

    那到此为止,我们可以知道前两次的state输出都是0了。那解释后面两state之前,我们来介绍一个概念,transaction(事务),react更新机制依赖事务,被事务所包裹。所谓事务就是一个高阶函数,用wrapper封装起来,然后通过事务提供的perform方法执行method,最后通过事务close掉。那前面两次setState的时候,我们通过setState的调用栈发现,前两次已经处于batchUpdate中了,因为React组件渲染到Dom中的过程就处于一个大事务中,然后isBatchUpdates标识为true,所以无法立即更新,而settimerout则没有这么操作,所以每次setState都会更新了,所以上面答案就是 0 0 2 3

    React16最新版本setState执行过程

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

    这里我们发现,还是调用enqueueSetState,接下来我们看下这个方法里面做了啥:

      enqueueSetState(inst, payload, callback) {
        const fiber = getInstance(inst);
        const currentTime = requestCurrentTimeForUpdate();
        const suspenseConfig = requestCurrentSuspenseConfig();
        const expirationTime = computeExpirationForFiber(
          currentTime,
          fiber,
          suspenseConfig,
        );
    
        const update = createUpdate(expirationTime, suspenseConfig);
        update.payload = payload;
        if (callback !== undefined && callback !== null) {
          if (__DEV__) {
            warnOnInvalidCallback(callback, 'setState');
          }
          update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleWork(fiber, expirationTime);
      },
    

    这里,先获取fiber实例,然后获取当前时间,这个当前时间,这个当前时间是这样算的:如果现在是处于react内,直接根据当前时间Now生成,如果是在react外(比如在浏览器原生事件内,那么这个期间发生的所有update都会被重置为同一个更新,当然时间也是一样的),然后是获取suspense配置,这个就不多说了,expirationTime根据currentTime生成,我们看下图源码,到期时间会根据当前fiber的mode或者是在渲染期间直接使用当前的渲染超时时间,或者如果配置了suspenseConfig,则根据suspenseConfig.timeoutMs生成,或者根据priorityLevel(任务优先级)生成超时。


    image.png

    接下来,执行createUpdate,这个主要是生成一个update对象,这个对象含有以下属性:

    let update: Update<*> = {
        expirationTime,
        suspenseConfig,
    
        tag: UpdateState,
        payload: null,
        callback: null,
    
        next: null,
        nextEffect: null,
      };
    
    

    然后执行enqueueUpdate,我们看下面源码,这里我们先获取alternate,这个参数是判断当前是否是一个fiber,那这个方法主要是初始化queue1和queue2,然后加入到appendUpdateToQueue

    export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
      // Update queues are created lazily.
      const alternate = fiber.alternate;
      let queue1;
      let queue2;
      if (alternate === null) {
        // There's only one fiber.
        queue1 = fiber.updateQueue;
        queue2 = null;
        if (queue1 === null) {
          queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        }
      } else {
        // There are two owners.
        queue1 = fiber.updateQueue;
        queue2 = alternate.updateQueue;
        if (queue1 === null) {
          if (queue2 === null) {
            // Neither fiber has an update queue. Create new ones.
            queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
            queue2 = alternate.updateQueue = createUpdateQueue(
              alternate.memoizedState,
            );
          } else {
            // Only one fiber has an update queue. Clone to create a new one.
            queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
          }
        } else {
          if (queue2 === null) {
            // Only one fiber has an update queue. Clone to create a new one.
            queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
          } else {
            // Both owners have an update queue.
          }
        }
      }
      if (queue2 === null || queue1 === queue2) {
        // There's only a single queue.
        appendUpdateToQueue(queue1, update);
      } else {
        // There are two queues. We need to append the update to both queues,
        // while accounting for the persistent structure of the list — we don't
        // want the same update to be added multiple times.
        if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
          // One of the queues is not empty. We must add the update to both queues.
          appendUpdateToQueue(queue1, update);
          appendUpdateToQueue(queue2, update);
        } else {
          // Both queues are non-empty. The last update is the same in both lists,
          // because of structural sharing. So, only append to one of the lists.
          appendUpdateToQueue(queue1, update);
          // But we still need to update the `lastUpdate` pointer of queue2.
          queue2.lastUpdate = update;
        }
      }
    
      if (__DEV__) {
        if (
          fiber.tag === ClassComponent &&
          (currentlyProcessingQueue === queue1 ||
            (queue2 !== null && currentlyProcessingQueue === queue2)) &&
          !didWarnUpdateInsideUpdate
        ) {
          warningWithoutStack(
            false,
            '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;
        }
      }
    }
    

    下面是appendUpdateToQueue,如果队列是空的,则queue队列的队头和队尾指针指向当前update,否则队尾指针指向当前的update。

    function appendUpdateToQueue<State>(
      queue: UpdateQueue<State>,
      update: Update<State>,
    ) {
      // Append the update to the end of the list.
      if (queue.lastUpdate === null) {
        // Queue is empty
        queue.firstUpdate = queue.lastUpdate = update;
      } else {
        queue.lastUpdate.next = update;
        queue.lastUpdate = update;
      }
    }
    

    最后我们回到主函数,执行scheduleWork

    export function scheduleUpdateOnFiber(
      fiber: Fiber,
      expirationTime: ExpirationTime,
    ) {
    //检测是否嵌套超过最大深度,如果超过就报"超过最大更新深度。
    // 当组件在componentWillUpdate或componentdiddupdate内重复调用setState时,可能会发生这种情况。
    // React限制嵌套更新的数量以防止无限循环。超过最大更新深度。当组件在useffect内部调用setState时,
    // 可能会发生这种情况,但useffect要么没有依赖数组,要么在每个呈现上都有一个依赖项更改"
      checkForNestedUpdates();
      warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
    // 查找要更新的fiber所依赖的Root,如果没有找到,则说明是处于卸载的组件里面。
      const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
      if (root === null) {
        warnAboutUpdateOnUnmountedFiberInDEV(fiber);
        return;
      }
    // 检测是否接受本次更新,如果更新时间小于渲染时间则终止,或者当前正在执行的root为空也终止
      checkForInterruption(fiber, expirationTime);
      recordScheduleUpdate();
    
      // 获取当前的任务优先级别
      const priorityLevel = getCurrentPriorityLevel();
    // 如果超时时间是sync
      if (expirationTime === Sync) {
        if (
          // Check if we're inside unbatchedUpdates 检查我们是否在未锁定的更新中
          (executionContext & LegacyUnbatchedContext) !== NoContext &&
          // Check if we're not already rendering  检查我们是否还没有渲染
          (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
          // 如果上述条件都符合,则在更新前要把当前挂起的交互数据进行保存
          schedulePendingInteractions(root, expirationTime);
    
          // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
          // root inside of batchedUpdates should be synchronous, but layout updates
          // should be deferred until the end of the batch.
          //执行更新
          performSyncWorkOnRoot(root);
        } else {
      //同步更新是立即执行的,除非我们已经在锁定更新中,如果是,这是跟传统方式一样的,放入同步会掉队列,等待当前交互完成
          ensureRootIsScheduled(root);
          schedulePendingInteractions(root, expirationTime);
          if (executionContext === NoContext) {
        
            flushSyncCallbackQueue();
          }
        }
      } else {
    // 使用此函数可为root安排任务。每个root只有一个任务;如果任务已被调度,
    // 我们将检查以确保现有任务的到期时间与root所工作的下一级的到期时间相同。
    // 这个函数在每次更新之前调用,并且在退出任务之前就被调用。
        ensureRootIsScheduled(root);
    // 在更新前要把当前挂起的交互数据进行保存
        schedulePendingInteractions(root, expirationTime);
      }
    
      if (
        (executionContext & DiscreteEventContext) !== NoContext &&
        // 仅考虑用户阻塞优先级或更高级别的更新
        // discrete, even inside a discrete event.
        (priorityLevel === UserBlockingPriority ||
          priorityLevel === ImmediatePriority)
      ) {
        // This is the result of a discrete event. Track the lowest priority
        // discrete update per root so we can flush them early, if needed.
        if (rootsWithPendingDiscreteUpdates === null) {
          rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
        } else {
          const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
          if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
            rootsWithPendingDiscreteUpdates.set(root, expirationTime);
          }
        }
      }
    }
    

    最后我们总结一下


    react16 setState机制.png

    相关文章

      网友评论

          本文标题:setState原理

          本文链接:https://www.haomeiwen.com/subject/zeonbctx.html