美文网首页react
react浅析4 hook之setState

react浅析4 hook之setState

作者: 百里哈哈 | 来源:发表于2020-09-09 13:41 被阅读0次

    与以往一样, 以一个简单的示例入手

    import React, {useState} from 'react';
     
    function HelloWord() {
        const [msg, setMsg] = useState('hello word');
        const [name, setName] = useState('ali');
    
        const chgValue = () => {
            let dateTmp =  Date.now();
            setMsg('hello' + dateTmp);
            setMsg("hi" + dateTmp);
            setName('ali' + dateTmp);
        }
        return (
            <>
                <div>{msg}</div>
                <div>{name}</div>
                <span onClick={chgValue}> click me</span>
            </>
        );
    }
    
    export default HelloWord
    
    

    useState调用

    我们知道不管是初建一个function组件还是update更新function组件时, 最终都要触发function方法来生成虚拟DOM。
    这也就是说初建和update更新调用useState会走不同的处理。其调用代码如下所示, 初建主要执行mountState方法, 而更新则使用updateState方法

    HooksDispatcherOnMountInDEV = {
        ...
        useState: function (initialState) {
          currentHookNameInDev = 'useState';
          mountHookTypesDev();
          var prevDispatcher = ReactCurrentDispatcher.current;
          ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
    
          try {
            return mountState(initialState);
          } finally {
            ReactCurrentDispatcher.current = prevDispatcher;
          }
        }
        ...
      };
    
    HooksDispatcherOnUpdateInDEV = {
        useState: function (initialState) {
          currentHookNameInDev = 'useState';
          updateHookTypesDev();
          var prevDispatcher = ReactCurrentDispatcher.current;
          ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
    
          try {
            return updateState(initialState);
          } finally {
            ReactCurrentDispatcher.current = prevDispatcher;
          }
        }
      };
    

    mountState

    1. mount的时候会新生成一个hook对象并将其挂载到hook链上, 同时将当前fiber的memoizedState与hook链建立连接
    2. 将初始值赋值给memoizedState以供渲染使用
    3. dispatch为一个闭包函数,它会将当前fiber及queue作为参数传入, 在调用该方法时可获取当前的fiber以供使用
    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];
    }
    
    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;
    }
    

    hook链如下图所示
    useState_memoizedState.png

    dispatchAction

    该方法用来触发action, 实现hook state的更新。 其主要功能如下

    1. 创建update对象,并将其挂载到hook的queue属性上
    2. 将Action的变更值放到update的eagerState属性上
    3. 将fiber推入scheduleWork任务调度中, 接下来会进入workLoop中进行调和、commit 最终实现view渲染。
    function dispatchAction(fiber, queue, action) {
      ...
      var update = {
        expirationTime: expirationTime,
        suspenseConfig: suspenseConfig,
        action: action,
        eagerReducer: null,
        eagerState: null,
        next: null
      };
    
      {
        update.priority = getCurrentPriorityLevel();
      } // Append the update to the end of the list.
    
    
      var pending = queue.pending;
    
      if (pending === null) {
        // This is the first update. Create a circular list.
        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) {
        // This is a render phase update. 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.
        didScheduleRenderPhaseUpdate = true;
        update.expirationTime = renderExpirationTime;
        currentlyRenderingFiber$1.expirationTime = renderExpirationTime;
      } else {
        if (fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)) {
          // The queue is currently empty, which means we can eagerly compute the
          // next state before entering the render phase. If the new state is the
          // same as the current state, we may be able to bail out entirely.
          var lastRenderedReducer = queue.lastRenderedReducer;
    
          if (lastRenderedReducer !== null) {
            var prevDispatcher;
    
            {
              prevDispatcher = ReactCurrentDispatcher.current;
              ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
            }
    
            try {
              var currentState = queue.lastRenderedState;
              var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute
              // it, on the update object. If the reducer hasn't changed by the
              // time we enter the render phase, then the eager state can be used
              // without calling the reducer again.
    
              update.eagerReducer = lastRenderedReducer;
              update.eagerState = eagerState;
    
              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.current = prevDispatcher;
              }
            }
          }
        }
    
        {
          // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
          if ('undefined' !== typeof jest) {
            warnIfNotScopedWithMatchingAct(fiber);
            warnIfNotCurrentlyActingUpdatesInDev(fiber);
          }
        }
    
        scheduleWork(fiber, expirationTime);
      }
    }
    

    updateState

    updateState只是对updateReducer方法的调用

    updateReducer

    1. 通过updateWorkInProgresHook获取当前的hook对象, 访问时通过next在hook链上依次取出即可。
    2. 遍历upate链更新, 完成后返回state数值, 和dispatch方法
    function updateReducer(reducer, initialArg, init) {
      var hook = updateWorkInProgressHook();
      var queue = hook.queue;
    
      if (!(queue !== null)) {
        {
          throw Error( "Should have a queue. This is likely a bug in React. Please file an issue." );
        }
      }
    
      queue.lastRenderedReducer = reducer;
      var current = currentHook; // The last rebase update that is NOT part of the base state.
    
      var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
    
      var pendingQueue = queue.pending;
    
      if (pendingQueue !== null) {
        // We have new updates that haven't been processed yet.
        // We'll add them to the base queue.
        if (baseQueue !== null) {
          // Merge the pending queue and the base queue.
          var baseFirst = baseQueue.next;
          var pendingFirst = pendingQueue.next;
          baseQueue.next = pendingFirst;
          pendingQueue.next = baseFirst;
        }
    
        current.baseQueue = baseQueue = pendingQueue;
        queue.pending = null;
      }
    
      if (baseQueue !== null) {
        // We have a queue to process.
        var first = baseQueue.next;
        var newState = current.baseState;
        var newBaseState = null;
        var newBaseQueueFirst = null;
        var newBaseQueueLast = null;
        var update = first;
    
        do {
          var updateExpirationTime = update.expirationTime;
    
          if (updateExpirationTime < renderExpirationTime) {
            ...
          } else {
            ...
    
            if (update.eagerReducer === reducer) {
              // If this update was processed eagerly, and its reducer matches the
              // current reducer, we can use the eagerly computed state.
              newState = update.eagerState;
            } else {
              var action = update.action;
              newState = reducer(newState, action);
            }
          }
    
          update = update.next;
        } while (update !== null && update !== first);
    
        if (newBaseQueueLast === null) {
          newBaseState = newState;
        } else {
          newBaseQueueLast.next = newBaseQueueFirst;
        } // Mark that the fiber performed work, but only if the new state is
        // different from the current state.
    
    
        if (!objectIs(newState, hook.memoizedState)) {
          markWorkInProgressReceivedUpdate();
        }
    
        hook.memoizedState = newState;
        hook.baseState = newBaseState;
        hook.baseQueue = newBaseQueueLast;
        queue.lastRenderedState = newState;
      }
    
      var dispatch = queue.dispatch;
      return [hook.memoizedState, dispatch];
    }
    

    updateWorkInProgressHook

    1. 我们知道在初次创建时会将hook链挂载到fiber上, 因此再次更新时只需从fiber上取得即可
    2. 当组件update再次进入到function方法时,第一次调用useSate currentHook无值, 通过alternate进行获取; 而再次调用useSate通过next获取即可。
    function updateWorkInProgressHook() {
      var nextCurrentHook;
    
      if (currentHook === null) {  
        var current = currentlyRenderingFiber$1.alternate;
    
        if (current !== null) {
          nextCurrentHook = current.memoizedState;
        } else {
          nextCurrentHook = null;
        }
      } else {
        nextCurrentHook = currentHook.next;
      }
    
      var nextWorkInProgressHook;
    
      if (workInProgressHook === null) {
        nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
      } else {
        nextWorkInProgressHook = workInProgressHook.next;
      }
    
      if (nextWorkInProgressHook !== null) {
        // There's already a work-in-progress. Reuse it.
        workInProgressHook = nextWorkInProgressHook;
        nextWorkInProgressHook = workInProgressHook.next;
        currentHook = nextCurrentHook;
      } else {
        // Clone from the current hook.
        if (!(nextCurrentHook !== null)) {
          {
            throw Error( "Rendered more hooks than during the previous render." );
          }
        }
    
        currentHook = nextCurrentHook;
        var newHook = {
          memoizedState: currentHook.memoizedState,
          baseState: currentHook.baseState,
          baseQueue: currentHook.baseQueue,
          queue: currentHook.queue,
          next: null
        };
    
        if (workInProgressHook === null) {
          // This is the first hook in the list.
          currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
        } else {
          // Append to the end of the list.
          workInProgressHook = workInProgressHook.next = newHook;
        }
      }
    
      return workInProgressHook;
    }
    

    相关文章

      网友评论

        本文标题:react浅析4 hook之setState

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