React(v17.0.2)Hooks之uesState(未完待续……)

157 阅读4分钟

useState

基本使用

function HelloWorld(){
    const [data,setData]=useState(0)
   return <div>
       <h1>{data}</h1>
       <button onClick={()=>{
             setData((data)=>{
              return data+=1
      })
       }}>自增+</button>
     </div>
}

useState顺序分解

useState---hooks钩子

useState: function (initialState) {   
        currentHookNameInDev = 'useState';
        mountHookTypesDev();   
        var prevDispatcher = ReactCurrentDispatcher$1.current; 
        ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
        try {
          return mountState(initialState);
        } finally {
          ReactCurrentDispatcher$1.current = prevDispatcher;
        }
      },
逐条分析:
1. currentHookNameInDev   当前hook名字在dev环境
2. function mountHookTypesDev() {
    {
      var hookName = currentHookNameInDev; //'useState'

      if (hookTypesDev === null) { //初始化执行这里
        hookTypesDev = [hookName]; //hookTypesDev存储hookTypesDev存储hook
      } else {
        hookTypesDev.push(hookName);
      }
    }
  }
3. ReactCurrentDispatcher$1.current //包含许多hooks-api函数
根据上下文源码:
var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher,
根据上下文源码:
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;//字面意思内部构件
      

mountState函数--返回hook.memoizedState和dispatch

function mountState(initialState) {
    var hook = mountWorkInProgressHook();

    if (typeof initialState === 'function') {
      // $FlowFixMe: Flow doesn't like mixed types
      initialState = initialState();
    }

    hook.memoizedState = hook.baseState = initialState;
    var queue = hook.queue = {
      pending: null,
      dispatch: null,
      lastRenderedReducer: basicStateReducer,
      lastRenderedState: initialState
    };
    var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
    return [hook.memoizedState, dispatch];
  }
 逐条分析:
 1. mountWorkInProgressHook 见下文
 2. function basicStateReducer(state, action) {
    // $FlowFixMe: Flow doesn't like mixed types
    return typeof action === 'function' ? action(state) : action;
  }

mountWorkInProgressHook函数--链式存储,fiber的memoizedState字段

 function mountWorkInProgressHook() {
    var hook = {
      memoizedState: null,
      baseState: null,
      baseQueue: null,
      queue: null,
      next: null
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list
      currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
    } else {
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    }

    return workInProgressHook; 
  }
 根据上下文:
     1. workInProgressHook 在render阶段任何时间节点是否计划更新,如果我们再做一次render渲染,这个不会重置,只有当我们完全完成对该组件的计算。我们知道这是一种优化我们是否需要在抛出后清除渲染阶段更新
     2. currentlyRenderingFiber$1 hooks已链表形式存储在fiber的memoizedState字段,当前hooks列表属于当前fiber列表,workInProgressHook列表是将被添加到workInProgress fiber的新列表

dispatchAction函数--setData

function dispatchAction(fiber, queue, action) {
    {
      if (typeof arguments[3] === 'function') {
        error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');
      }
    }

    var eventTime = requestEventTime();
    var lane = requestUpdateLane(fiber);
    var update = {
      lane: lane,
      action: action,
      eagerReducer: null,
      eagerState: null,
      next: null
    }; // 将更新追加到列表的末尾

    var pending = queue.pending;

    if (pending === null) {
      // 第一次更新,创建一个循环列表
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }

    queue.pending = update;
    var alternate = fiber.alternate; //备用节点

    if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
      // 这是一个渲染阶段更新. Stash it in a lazily-created map of
      // queue -> linked list of updates. After this render pass, we'll restart
      // and apply the stashed updates on top of the work-in-progress hook.
      didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
    } else {
      if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
        // 队列当前为空, 这意味着在进入渲染阶段之前,我们可以急切的计算下一个状态. 如果新状态和当前状态一样,我们或许能够完全的退出.
        var lastRenderedReducer = queue.lastRenderedReducer; //basicStateReducer函数

        if (lastRenderedReducer !== null) {
          var prevDispatcher;

          {
            prevDispatcher = ReactCurrentDispatcher$1.current;
            ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;  //hooks相关API
          }

          try {
            var currentState = queue.lastRenderedState; //初始化状态0
            var eagerState = lastRenderedReducer(currentState, action); // 即时储放计算的状态, reducer 被用于计算state, 更新对象. 如果reducer在渲染阶段的时候没有被改变,eagerState将被使用,不需要reduceer再次调用了
            
            update.eagerReducer = lastRenderedReducer;
            update.eagerState = eagerState; //计算后返回值1

            if (objectIs(eagerState, currentState)) { //判断是否是对
              // Fast path. We can bail out without scheduling React to re-render.
              // It's still possible that we'll need to rebase this update later,
              // if the component re-renders for a different reason and by that
              // time the reducer has changed.
              return;
            }
          } catch (error) {// Suppress the error. It will throw again in the render phase.
          } finally {
            {
              ReactCurrentDispatcher$1.current = prevDispatcher; //存dispatcher
            }
          }
        }
      }

      {
        // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
        if ('undefined' !== typeof jest) {
          warnIfNotScopedWithMatchingAct(fiber);
          warnIfNotCurrentlyActingUpdatesInDev(fiber);
        }
      }

      scheduleUpdateOnFiber(fiber, lane, eventTime);
    }
  }

requestEventTime函数--协调过程中所需要的时间

function requestEventTime() {
    if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
      // 我们在React内部,所以可以读取实际时间
      return now();
    } 
    // 我们不在React内部,所以我们可能正处于浏览器事件的中间.
    if (currentEventTime !== NoTimestamp) {
      //所有更新都使用相同的开始时间,直到我们再次进入React
      return currentEventTime;
    } 
    // 这是React产生以来的第一次更新。计算一个新的开始时间.
    currentEventTime = now();
    return currentEventTime;
  }

requestUpdateLane函数--更新优先级

function requestUpdateLane(fiber) {
    // Special cases
    var mode = fiber.mode;  //当前状态8

    if ((mode & BlockingMode) === NoMode) {
      return SyncLane;  
    } else if ((mode & ConcurrentMode) === NoMode) {
      return getCurrentPriorityLevel() === ImmediatePriority$1 ? SyncLane : SyncBatchedLane;
    } // The algorithm for assigning an update to a lane should be stable for all
    // updates at the same priority within the same event. To do this, the inputs
    // to the algorithm must be the same. For example, we use the `renderLanes`
    // to avoid choosing a lane that is already in the middle of rendering.
    //
    // However, the "included" lanes could be mutated in between updates in the
    // same event, like if you perform an update inside `flushSync`. Or any other
    // code path that might call `prepareFreshStack`.
    //
    // The trick we use is to cache the first of each of these inputs within an
    // event. Then reset the cached values once we can be sure the event is over.
    // Our heuristic for that is whenever we enter a concurrent work loop.
    //
    // We'll do the same for `currentEventPendingLanes` below.


    if (currentEventWipLanes === NoLanes) {
      currentEventWipLanes = workInProgressRootIncludedLanes;
    }

    var isTransition = requestCurrentTransition() !== NoTransition;

    if (isTransition) {
      if (currentEventPendingLanes !== NoLanes) {
        currentEventPendingLanes = mostRecentlyUpdatedRoot !== null ? mostRecentlyUpdatedRoot.pendingLanes : NoLanes;
      }

      return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);
    } // TODO: Remove this dependency on the Scheduler priority.
    // To do that, we're replacing it with an update lane priority.


    var schedulerPriority = getCurrentPriorityLevel(); // The old behavior was using the priority level of the Scheduler.
    // This couples React to the Scheduler internals, so we're replacing it
    // with the currentUpdateLanePriority above. As an example of how this
    // could be problematic, if we're not inside `Scheduler.runWithPriority`,
    // then we'll get the priority of the current running Scheduler task,
    // which is probably not what we want.

    var lane;

    if ( // TODO: Temporary. We're removing the concept of discrete updates.
    (executionContext & DiscreteEventContext) !== NoContext && schedulerPriority === UserBlockingPriority$2) {
      lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);
    } else {
      var schedulerLanePriority = schedulerPriorityToLanePriority(schedulerPriority);

      lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);
    }

    return lane;
  }
  逐条分析:
  1. lan模型代表执行优先级,SyncLane代表同步优先级为1
  

scheduleUpdateOnFiber函数

function scheduleUpdateOnFiber(fiber, lane, eventTime) {
    checkForNestedUpdates();
    warnAboutRenderPhaseUpdatesInDEV(fiber);
    var root = markUpdateLaneFromFiberToRoot(fiber, lane);

    if (root === null) {
      warnAboutUpdateOnUnmountedFiberInDEV(fiber);
      return null;
    } // 标志着root有一个pending更新


    markRootUpdated(root, lane, eventTime); //见如下

    if (root === workInProgressRoot) {
      // Received an update to a tree that's in the middle of rendering. Mark
      // that there was an interleaved update work on this root. Unless the
      // `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
      // phase update. In that case, we don't treat render phase updates as if
      // they were interleaved, for backwards compat reasons.
      {
        workInProgressRootUpdatedLanes = mergeLanes(workInProgressRootUpdatedLanes, lane);
      }

      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        // The root already suspended with a delay, which means this render
        // definitely won't finish. Since we have a new update, let's mark it as
        // suspended now, right before marking the incoming update. This has the
        // effect of interrupting the current render and switching to the update.
        // TODO: Make sure this doesn't override pings that happen while we've
        // already started rendering.
        markRootSuspended$1(root, workInProgressRootRenderLanes);
      }
    } // TODO: requestUpdateLanePriority also reads the priority. Pass the
    // priority as an argument to that function and this one.


    var priorityLevel = getCurrentPriorityLevel();//获取当前优先级等级

    if (lane === SyncLane) {
      if ( //检查我们是否在unbatchedUpdates里
      (executionContext & LegacyUnbatchedContext) !== NoContext && //检查我们是否还没有渲染
      (executionContext & (RenderContext | CommitContext)) === NoContext) {
        // Register pending interactions on the root to avoid losing traced interaction data.
        schedulePendingInteractions(root, lane); // 这是一个遗留问题. ReactDOM 初始化mount.在batchedUpdates里面渲染的root应该是同步的, 但布局更新应该延迟到批处理结束.
        performSyncWorkOnRoot(root);
      } else {
        ensureRootIsScheduled(root, eventTime);
        schedulePendingInteractions(root, lane);

        if (executionContext === NoContext) {
          // Flush the synchronous work now, unless we're already working or inside
          // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
          // scheduleCallbackForFiber to preserve the ability to schedule a callback
          // without immediately flushing it. We only do this for user-initiated
          // updates, to preserve historical behavior of legacy mode.
          resetRenderTimer();
          flushSyncCallbackQueue();
        }
      }
    } else {
      // Schedule a discrete update but only if it's not Sync.
      if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
      // discrete, even inside a discrete event.
      priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority$1)) {
        // 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 Set([root]);
        } else {
          rootsWithPendingDiscreteUpdates.add(root);
        }
      } // Schedule other updates after in case the callback is sync.


      ensureRootIsScheduled(root, eventTime);
      schedulePendingInteractions(root, lane);
    } // We use this when assigning a lane for a transition inside
    // `requestUpdateLane`. We assume it's the same as the root being updated,
    // since in the common case of a single root app it probably is. If it's not
    // the same root, then it's not a huge deal, we just might batch more stuff
    // together more than necessary.


    mostRecentlyUpdatedRoot = root;
  } // 这个函数被分割成一个单独的函数,因此我们可以标记一个fiber在pending工作时,,而不把它作为一个典型的更新,起源于一个事件;例如,重新尝试一个未知边界而不是一个更新,但它确实协调工作在fiber上

markUpdateLaneFromFiberToRoot函数--根节点(root)调度任务

使用这个函数为根节点(root)调度任务,每个根节点只有一个任务;如果一个任务已经被调度,我们将检查以确保现有任务的优先级与根节点所工作的下一级别的优先级相同。这个函数在每次更新时调用,并且在退出任务之前调用。
function markUpdateLaneFromFiberToRoot(sourceFiber, lane) {
        // 更新原fiber的优先级
    sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
    var alternate = sourceFiber.alternate;

    if (alternate !== null) {
      alternate.lanes = mergeLanes(alternate.lanes, lane);
    }

    {
      if (alternate === null && (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags) {
        warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
      }
    } // Walk the parent path to the root and update the child expiration time.


    var node = sourceFiber; //原fiber节点
    var parent = sourceFiber.return; //父级fiber节点

    while (parent !== null) { //由内到外调整渲染优先级lan
      parent.childLanes = mergeLanes(parent.childLanes, lane); 
      alternate = parent.alternate; 

      if (alternate !== null) {
        alternate.childLanes = mergeLanes(alternate.childLanes, lane);
      } else {
        {
          if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
            warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
          }
        }
      }

      node = parent;
      parent = parent.return;
    }

    if (node.tag === HostRoot) { //最外root节点
      var root = node.stateNode;
      return root;
    } else {
      return null;
    }
  } 

markRootUpdated函数--标记根节点更新相关属性

 function markRootUpdated(root, updateLane, eventTime) {
        root.pendingLanes |= updateLane; // 理论上任何lan的更新都可以解除其他lan的阻塞. 但尝试所有可能的组合是不实际的.我们需要一个启发式决定哪些lanes试图渲染,在哪些批次.
        现在,我们使用与旧的过期时间模型相同的启发式方法:重试一些lane在相同或者较低优先级,
        但是不要尝试更新更高优先级的,不包含低优先级的更新。这工作得很好当考虑跨越不同优先级的更新时,但不是对于相同优先级的更新来说足够了,因为我们想要处理的这些更新是并行的。解除所有同等或更低优先级的更新。

    var higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111

    root.suspendedLanes &= higherPriorityLanes;
    root.pingedLanes &= higherPriorityLanes;
    var eventTimes = root.eventTimes;
    var index = laneToIndex(updateLane); // 我们总是可以覆盖现有的时间戳,因为我们最喜欢
最近的事件,我们假设时间是单调递增的。
    eventTimes[index] = eventTime;
  }

performSyncWorkOnRoot

function performSyncWorkOnRoot(root) {
    if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
      {
        throw Error( "Should not already be working." );
      }
    }

    flushPassiveEffects();
    var lanes;
    var exitStatus;

    if (root === workInProgressRoot && includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)) {
      // There's a partial tree, and at least one of its lanes has expired. Finish
      // rendering it before rendering the rest of the expired work.
      lanes = workInProgressRootRenderLanes;
      exitStatus = renderRootSync(root, lanes);

      if (includesSomeLane(workInProgressRootIncludedLanes, workInProgressRootUpdatedLanes)) {
        // The render included lanes that were updated during the render phase.
        // For example, when unhiding a hidden tree, we include all the lanes
        // that were previously skipped when the tree was hidden. That set of
        // lanes is a superset of the lanes we started rendering with.
        //
        // Note that this only happens when part of the tree is rendered
        // concurrently. If the whole tree is rendered synchronously, then there
        // are no interleaved events.
        lanes = getNextLanes(root, lanes);
        exitStatus = renderRootSync(root, lanes);
      }
    } else {
          lanes = getNextLanes(root, NoLanes);
      exitStatus = renderRootSync(root, lanes);
    }

    if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
      executionContext |= RetryAfterError; // If an error occurred during hydration,
      // discard server response and fall back to client side render.

      if (root.hydrate) {
        root.hydrate = false;
        clearContainer(root.containerInfo);
      } // If something threw an error, try rendering one more time. We'll render
      // synchronously to block concurrent data mutations, and we'll includes
      // all pending updates are included. If it still fails after the second
      // attempt, we'll give up and commit the resulting tree.


      lanes = getLanesToRetrySynchronouslyOnError(root);

      if (lanes !== NoLanes) {
        exitStatus = renderRootSync(root, lanes);
      }
    }

    if (exitStatus === RootFatalErrored) {
      var fatalError = workInProgressRootFatalError;
      prepareFreshStack(root, NoLanes);
      markRootSuspended$1(root, lanes);
      ensureRootIsScheduled(root, now());
      throw fatalError;
    } // We now have a consistent tree. Because this is a sync render, we
    // will commit it even if something suspended.


    var finishedWork = root.current.alternate;
    root.finishedWork = finishedWork;
    root.finishedLanes = lanes;
    commitRoot(root); //退出前, 确保callback被预定在下一次的pending等级

    ensureRootIsScheduled(root, now());
    return null;
  }

ensureRootIsScheduled

 function ensureRootIsScheduled(root, currentTime) {
    var existingCallbackNode = root.callbackNode; // 检查一下是否有lanes被其他工作占用了. 如果这样, 他们标记为过期,这样我们就知道下一步要做什么.

    markStarvedLanesAsExpired(root, currentTime); //确定下一个要处理的lanes,以及他们的优先级

    var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes); // 这将返回在' getNextLanes '调用期间计算的优先级。

    var newCallbackPriority = returnNextLanesPriority();

    if (nextLanes === NoLanes) {
      // Special case: There's nothing to work on.
      if (existingCallbackNode !== null) {
        cancelCallback(existingCallbackNode);
        root.callbackNode = null;
        root.callbackPriority = NoLanePriority;
      }

      return;
    } // 检查是否有一个现有的任务。我们也许可以重新利用它。.


    if (existingCallbackNode !== null) {
      var existingCallbackPriority = root.callbackPriority;

      if (existingCallbackPriority === newCallbackPriority) {
        // The priority hasn't changed. We can reuse the existing task. Exit.
        return;
      } // The priority changed. Cancel the existing callback. We'll schedule a new
      // one below.


      cancelCallback(existingCallbackNode);
    } // Schedule a new callback.


    var newCallbackNode;

    if (newCallbackPriority === SyncLanePriority) {
      // Special case: Sync React callbacks are scheduled on a special
      // internal queue
      newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else if (newCallbackPriority === SyncBatchedLanePriority) {
      newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));
    } else {
      var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
      newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
    }

    root.callbackPriority = newCallbackPriority;
    root.callbackNode = newCallbackNode;
  } // 这是每个并发任务的入口点, 即任何通过调度程序.