美文网首页react
react浅析2 setState过程

react浅析2 setState过程

作者: 百里哈哈 | 来源:发表于2020-09-08 09:34 被阅读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的更新

    相关文章

      网友评论

        本文标题:react浅析2 setState过程

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