setState原理

2019-11-09  本文已影响0人  木中木
 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
上一篇下一篇

猜你喜欢

热点阅读