useState以及部分源码理解

61 阅读7分钟

如果发现有问题或理解不到位等,请及时联系作者、评论指出

const initValue = 0 //默认值
const [count,setCount] = useState(initValue) // 声明
setCount(1) // 更新下次渲染的count值为 无返回值

function useState<S>(initialState: (() => S) | S,)
:[S, Dispatch<BasicStateAction<S>>]
{
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}

ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount // 挂载
: HooksDispatcherOnUpdate; // 更新

function mountState(initialState){
  // TO SKIP 
  const hook = mountWorkInProgressHook();
  //判断mountState传入的initialState是函数还是值,是函数就运行将返回值赋值给initialState
  if(typeof initialState === 'function'){
     initialState = initialState();
  }
  //接下来,将initialState赋值给hook的baseState和hook的memoizedState
  hook.memoizedState = hook.baseState = initialState;
  const queue = {
    pending: null, // 等待
    lanes: NoLanes,  // 更新优先级
    dispatch: null, // setState方法
    lastRenderedReducer: basicStateReducer, // TO SKIP  
    lastRenderedState: (initialState: any),
  };
  //将创建的queue赋值给hook的queue
  hook.queue = queue;
  // TO SKIP
  const dispatch = 
    (queue.dispatch =
     (dispatchSetState.bind(null,currentlyRenderingFiber,queue,)));
  return [hook.memoizedState, dispatch];
}

//首先调用mountWorkInProgressHook,创建一个hook存储状态,如果当前工作中没有钩子,
//就将创建的hook赋值给当前工作中钩子,如果当前工作中有钩子,就将当前hook赋值给当前工
//作中钩子的next(相当于链表的next),最后返回当前工作中的hook,并用hook变量储存后续使用。
function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
 //如果是方法则调用否则返回传入的
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

function dispatchSetState<S, A>(fiber: Fiber,queue: UpdateQueue<S, A>,action: A,){
   const lane = requestUpdateLane(fiber); // 更新优先级
    const update: Update<S, A> = {
    lane, // 更新优先级
    action, // 更新动作
    hasEagerState: false, // 是否紧急更新
    eagerState: null, // 紧急更新对象
    next: (null: any),
  };
  //判断传入的fiber是否在渲染阶段更新
    if (isRenderPhaseUpdate(fiber)) {
    // TO SKIP
    enqueueRenderPhaseUpdate(queue, update);
  } else{
    //获取当前fiber节点的alternate节点(当前fiber节点的上一个节点)
    const alternate = fiber.alternate;
    //判断当前fiber节点的lanes属性是否为NoLanes并且判断alternate节点是否为空或者
    //其lanes属性是否为NoLanes,其作用是用来确定当前任务是否需要被渲染
    if(fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)){
      //判断lastRenderedReducer(队列)是否为空
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        try {
        //从queue对象的lastRenderedState属性获取当前状态,
        //并将其赋值给名为currentState的常量
        const currentState = queue.lastRenderedState;
        //使用currentState和传入的action,
        //通过调用lastRenderedReducer函数来计算新的状态
        const eagerState = lastRenderedReducer(currentState, action);
        //将eagerState(急切状态)以及还原器已经执行过的标志(hasEagerState)
        //存储在update
        update.hasEagerState = true;
        update.eagerState = eagerState;
        //判断急切状态是否与当前状态相同。
        if (is(eagerState, currentState)) {
          //调用enqueueConcurrentHookUpdateAndEagerlyBailout函数
          //来调度一个优化后的更新操作
          //并直接返回,不再触发React组件的重新渲染
          enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
          return;
          }
        }
      }
    }
    //TO SKIP
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      //进行一系列调度更新
      const eventTime = requestEventTime(); // 异步、合并更新原理
      scheduleUpdateOnFiber(root, fiber, lane, eventTime); // 调度更新
      entangleTransitionUpdate(root, queue, lane); // 调度更新
    }
  }
   markUpdateInDevTools(fiber, lane, action); // 调度更新
}

function enqueueRenderPhaseUpdate(queue,update) {
  didScheduleRenderPhaseUpdateDuringThisPass //当前遍历fiber树的过程中是否
     //发生了 render phase update
    = didScheduleRenderPhaseUpdate //当前渲染过程中,是否存在多个组件尝试触发状态更新
    = true;
  const pending = queue.pending;
  if (pending === null) {
    // 第一次更新,并且创建一个循环的列表
    update.next = update;
  } else {
    //向循环列表插入update
    update.next = pending.next;
    pending.next = update;
  }
  //将update赋值给queue的pending
  queue.pending = update;
}

function enqueueConcurrentHookUpdate(fiber,queue,update,lane):FiberRoot{
   // TO SKIP
   enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
   return getRootForUpdatedFiber(fiber);
}

//将更新任务加入到并发队列中,同时更新相关的lanes信息
function enqueueUpdate(fiber,queue,update,lane,) {
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;
  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}

function getRootForUpdatedFiber(sourceFiber): FiberRoot | null {
  let node = sourceFiber;//将传入的fiber储存为node
  let parent = node.return;//将node的return属性(父fiber)存储为parent
  //如果parent不为空则一直去找他的父fiber,直到为null为止(找到fiber根结点)
  while (parent !== null) {
    node = parent;
    parent = node.return;
  }
  //如果node.tag等于HostRoot(根结点),则返回node.stateNode。
  //否则(即未找到根节点或不是HostRoot类型),返回null
  return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}

个人理解(已同步到源码中,可忽略)

useStat是通过调用resolveDispatcher返回dispatcher的userState,通过当前状态判断是否更新或者挂载。挂载:HooksDispatcherOnMount调用mountState返回[hook.memoizedState, dispatch]

首先调用mountWorkInProgressHook,创建一个hook存储状态,如果当前工作中没有钩子,就将创建的hook赋值给当前工作中钩子,如果当前工作中有钩子,就将当前hook赋值给当前工作中钩子的next(相当于链表的next),最后返回当前工作中的hook,并用hook变量储存后续使用。

其次,判断mountState传入的initialState是函数还是值,如果是函数就运行将返回值赋值给initialState。

接下来,将initialState赋值给hook的baseState和hook的memoizedState。

接下来,创建一个队列queue,属性有pending(等待),lanes(优先级),dispatch(setState方法),

lastRenderedReducer(basicStateReducer,就是reducer的一种类型),lastRenderedState(initialState),将创建的queue赋值给hook的queue。

接下来,调用dispatchSetState方法,更新优先级,创建update,属性有lane(优先级),action(动作),hasEagerState(是否紧急更新),eagerState(紧急更新对象),next(下一个)。判断传入的fiber是否在渲染阶段更新。

如果在,调用enqueueRenderPhaseUpdate,将didScheduleRenderPhaseUpdate(当前批次的渲染过程中,是否存在一个或多个组件尝试触发状态更新)和didScheduleRenderPhaseUpdateDuringThisPass(当前遍历fiber树的过程中是否发生了render phase update),接着判断queue的pending是否为null(则为第一次更新),如果为null,是第一次更新,并且创建一个循环的列表。如果不为null,则将pending的next赋值给update的next,将update赋值给pending的next,构成一个循环列表。最后将update赋值给queue的pending。

如果不在,获取当前fiber节点的alternate节点,alternate节点是当前fiber节点的上一个节点,接着判断当前fiber节点的lanes属性是否为NoLanes并且判断alternate节点是否为空或者其lanes属性是否为NoLanes,其作用是用来确定当前任务是否需要被渲染。如果需要被渲染,判断lastRenderedReducer(队列)是否为空,如果不为空,则将从queue对象的lastRenderedState属性获取当前状态,并将其赋值给名为currentState的常量,使用上一步获取到的当前状态和传入的动作(action),通过调用lastRenderedReducer函数来计算新的状态,将“急切状态”(eagerState)以及还原器已经执行过的标志(hasEagerState)存储在update对象中。这样,在进入渲染阶段时,如果还原器没有改变,则可以直接使用这个急切状态而无需再次调用还原器。接下来,判断急切状态是否与当前状态相同。如果两者相等,意味着当前动作并未引起状态的实际变化,函数会选择快速路径,通过调用enqueueConcurrentHookUpdateAndEagerlyBailout函数来调度一个优化后的更新操作,并直接返回,不再触发React组件的重新渲染。接着调用enqueueConcurrentHookUpdate方法,里面调用enqueueUpdate将更新任务加入到并发队列中,同时更新相关的lanes信息。返回getRootForUpdatedFiber,这个方法将传入的fiber储存为node,同时将node的return属性(父fiber)

存储为parent,如果parent不为空则一直去找他的父fiber,直到为null为止(找到fiber根结点),如果node.tag等于HostRoot(根结点),则返回node.stateNode。否则(即未找到根节点或不是HostRoot类型),返回null。

接下来,如果返回的根节点不为nul,则进行一系列调度更新

实现小Demo(与源码会有偏差,只做理解参考)

function myUseState() {
  let isMount = true;
  let workInProgressHook; 

  const fiber = {
    stateNode: App,
    memorizedState: null, 
  };

  function useState(initialState) {
    let hook;
    if (isMount) {
      hook = {
        memorizedState: initialState,
        next: null,
        queue: {
          pending: null,
        },
      };
      if (!fiber.memorizedState) {
        fiber.memorizedState = hook;
      } else {
        workInProgressHook.next = hook;
      }
      workInProgressHook = hook;
    } else {
      hook = workInProgressHook;
      workInProgressHook = workInProgressHook.next;
    }

    let baseState = hook.memorizedState;
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;
      do {
        const action = firstUpdate.action;
        if (typeof action === "function") {
          baseState = action(baseState);
        } else {
          baseState = action;
        }
        firstUpdate = firstUpdate.next;
      } while (firstUpdate !== hook.queue.pending);
      hook.queue.pending = null;
    }
    hook.memorizedState = baseState;
    return [baseState, dispatchAction.bind(null, hook.queue)];
  }

  function schedule() {
    workInProgressHook = fiber.memorizedState;
    const app = fiber.stateNode();
    isMount = false;
    return app;
  }

  function dispatchAction(queue, action) {
    const updateState = {
      action,
      next: null,
    };
    if (queue.pending === null) {
      updateState.next = updateState;
    } else {
      updateState.next = queue.pending.next;
      queue.pending.next = updateState;
    }
    queue.pending = updateState;
    schedule();
  }
  function App() {
    const [num, updateNum] = myUseState(0);
    const [num1, updateNum1] = myUseState(0);

    console.log(`${isMount ? "mount" : "update"} num: `, num);
    console.log("num1:", num1);
    return {
      click() {
        updateNum((num) => num + 10);
        updateNum1(20);
      },
    };
  }
  window.app = schedule();
}