react

react浅析2 setState过程

2020-09-08  本文已影响0人  百里哈哈

首先我们以一个简单的示例来分析其大概流程
示例如下

class SetTest extends React.Component {
    constructor (props) {
        super(props)
        this.state = {
            name: 'ali',
            age: 18
        }
    }
    chgValue = () => {
        this.setState({
            name: 'alinew' + Date.now()
        })
        this.setState({
            age: 16
        })
    }
    render() {
        return (
            <div>
                <span>name is {this.state.name}</span><br/>
                <span>age is {this.state.age}</span>
                <p>
                    <button onClick={this.chgValue}>click me</button>
                </p>
            </div>
        )
    }
}

export default SetTest

在该示例值只是通过click来触发setState。
其函数的调用流程大体如下所示


setState.png

setState.png

有图可知setState分为两个阶段

  1. 该示例中通过click触发的setState为异步任务, 所以在刚开始的时候会将一系列的update挂载到其对应fiber的updateQueue链上。接着会创建一个任务并将该任务推入 schedule任务调度流中。 等待浏览器空余时间执行其中的work任务
  2. 通过flushSyncCallbackQueue执行当前需要执行的任务, 进入workLoopSync循环之中。 开始beginWork后执行的仍旧是reconcile调和过程、commit提交副作用阶段

enqueueSetState

该方法主要功能有

  1. enqueueUpdate将需要更新的对象放入fiber的更新队列中
  2. 进入任务调度阶段, 将需要执行的任务放入任务队列中
enqueueSetState: function (inst, payload, callback) {
   var fiber = get(inst);
   var currentTime = requestCurrentTimeForUpdate();
   var suspenseConfig = requestCurrentSuspenseConfig();
   var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);
   var update = createUpdate(expirationTime, suspenseConfig);
   update.payload = payload;

   if (callback !== undefined && callback !== null) {
     {
       warnOnInvalidCallback(callback, 'setState');
     }

     update.callback = callback;
   }

   enqueueUpdate(fiber, update);
   scheduleWork(fiber, expirationTime);
 }

ensureRootIsScheduled

  1. 首先判断当前时刻有是否需要执行的work, 如果没有则返回
  2. 如果当前有需要同步执行的任务放入scheduleSyncCallback
  3. 如果当然任务为异步的话, 则按照优先级放入异步队列中
function ensureRootIsScheduled(root) {
 var lastExpiredTime = root.lastExpiredTime;

 if (lastExpiredTime !== NoWork) {
   // Special case: Expired work should flush synchronously.
   root.callbackExpirationTime = Sync;
   root.callbackPriority = ImmediatePriority;
   root.callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
   return;
 }

 var expirationTime = getNextRootExpirationTimeToWorkOn(root);
 var existingCallbackNode = root.callbackNode;

 if (expirationTime === NoWork) {
   // There's nothing to work on.
   if (existingCallbackNode !== null) {
     root.callbackNode = null;
     root.callbackExpirationTime = NoWork;
     root.callbackPriority = NoPriority;
   }

   return;
 } // TODO: If this is an update, we already read the current time. Pass the
 // time as an argument.


 var currentTime = requestCurrentTimeForUpdate();
 var priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); // If there's an existing render task, confirm it has the correct priority and
 // expiration time. Otherwise, we'll cancel it and schedule a new one.

 ...

 if (expirationTime === Sync) {
   // Sync React callbacks are scheduled on a special internal queue
   callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
 } else {
   callbackNode = scheduleCallback(priorityLevel, performConcurrentWorkOnRoot.bind(null, root), // Compute a task timeout based on the expiration time. This also affects
   // ordering because tasks are processed in timeout order.
   {
     timeout: expirationTimeToMs(expirationTime) - now()
   });
 }

 root.callbackNode = callbackNode;
} 

scheduleSyncCallback

对于同步执行的callback函数将其放入syncQueue, 同时在这里我们可以看到flushSyncCallbackQueueImpl作为回调函数传入到Scheduler_scheduleCallback方法中,以供后续调用

function scheduleSyncCallback(callback) {
 // Push this callback into an internal queue. We'll flush these either in
 // the next tick, or earlier if something calls `flushSyncCallbackQueue`.
 if (syncQueue === null) {
   syncQueue = [callback]; // Flush the queue in the next tick, at the earliest.

   immediateQueueCallbackNode = Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueueImpl);
 } else {
   // Push onto existing queue. Don't need to schedule a callback because
   // we already scheduled one when we created the queue.
   syncQueue.push(callback);
 }

 return fakeCallbackNode;
}

flushSyncCallbackQueueImpl

通过该方法可以清楚的看出它取出syncQueue队列并按照ImmediatePriority(立即执行)的优先级执行其中的队列任务。
以该示例来说它的队列任务为[performSyncWorkOnRoot]会进入的同步work的调度中。

function flushSyncCallbackQueueImpl() {
 if (!isFlushingSyncQueue && syncQueue !== null) {
   // Prevent re-entrancy.
   isFlushingSyncQueue = true;
   var i = 0;

   try {
     var _isSync = true;
     var queue = syncQueue;
     runWithPriority$1(ImmediatePriority, function () {
       for (; i < queue.length; i++) {
         var callback = queue[I];

         do {
           callback = callback(_isSync);
         } while (callback !== null);
       }
     });
     syncQueue = null;
   } catch (error) {
     // If something throws, leave the remaining callbacks on the queue.
     if (syncQueue !== null) {
       syncQueue = syncQueue.slice(i + 1);
     } // Resume flushing in the next tick


     Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue);
     throw error;
   } finally {
     isFlushingSyncQueue = false;
   }
 }
}

updateClassComponent

在setState更新阶段, updateClassInstance对该组件实例属性进行更新, 如执行updateQueue链更新其对应的state变更的值。
state更新完成后, 需要生成新的虚拟DOM这一系列的过程在finishClassComponent
render完成后会返回组件的根节点fiber作为下一次迭代的workInProgress

function updateClassComponent(current, workInProgress, Component, nextProps, renderExpirationTime) {
 ...

 if (instance === null) {
   if (current !== null) {
     // A class component without an instance only mounts if it suspended
     // inside a non-concurrent tree, in an inconsistent state. We want to
     // treat it like a new mount, even though an empty version of it already
     // committed. Disconnect the alternate pointers.
     current.alternate = null;
     workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect

     workInProgress.effectTag |= Placement;
   } // In the initial pass we might need to construct the instance.


   constructClassInstance(workInProgress, Component, nextProps);
   mountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
   shouldUpdate = true;
 } else if (current === null) {
   // In a resume, we'll already have an instance we can reuse.
   shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
 } else {
   shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderExpirationTime);
 }

 var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);

 ...
 return nextUnitOfWork;
}

updateHostComponent

再一次的workInProgress进入的组件根fiber中, 此时会触发updateHostComponent函数。
通过该方法进入到 reconcileChildren调和阶段, 此后的阶段与上一节渲染分析一致在此不做赘述。

function updateHostComponent(current, workInProgress, renderExpirationTime) {
 pushHostContext(workInProgress);

 if (current === null) {
   tryToClaimNextHydratableInstance(workInProgress);
 }

 var type = workInProgress.type;
 var nextProps = workInProgress.pendingProps;
 var prevProps = current !== null ? current.memoizedProps : null;
 var nextChildren = nextProps.children;
 var isDirectTextChild = shouldSetTextContent(type, nextProps);

 if (isDirectTextChild) {
   // We special case a direct text child of a host node. This is a common
   // case. We won't handle it as a reified child. We will instead handle
   // this in the host environment that also has access to this prop. That
   // avoids allocating another HostText fiber and traversing it.
   nextChildren = null;
 } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
   // If we're switching from a direct text child to a normal child, or to
   // empty, we need to schedule the text content to be reset.
   workInProgress.effectTag |= ContentReset;
 }

 markRef(current, workInProgress); // Check the host config to see if the children are offscreen/hidden.

 if (workInProgress.mode & ConcurrentMode && renderExpirationTime !== Never && shouldDeprioritizeSubtree(type, nextProps)) {
   {
     markSpawnedWork(Never);
   } // Schedule this fiber to re-render at offscreen priority. Then bailout.


   workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
   return null;
 }

 reconcileChildren(current, workInProgress, nextChildren, renderExpirationTime);
 return workInProgress.child;
}

由该示例可知当前div下有4个孩子节点,workLoop之后其中的span1、 span2会涉及到变更。
最终会生成两个effect副作用, 在commit阶段进行DOM的变更处理。 两个effect的effectTag为4即对应commitTextUpdate的更新

上一篇 下一篇

猜你喜欢

热点阅读