reacthooks阅读

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

reacthooks-useState

源代码:github.com/facebook/re…

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

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}
复制代码

resolveDispatcher

function resolveDispatcher() {
  //import ReactCurrentDispatcher from './ReactCurrentDispatcher';
  const dispatcher = ReactCurrentDispatcher.current;
  //如果没有dispatcher调度器,就会抛出固定格式的错误
  //其中允许使用 %s 作为变量的占位符
  invariant(
    dispatcher !== null,
    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
      ' one of the following reasons:\n' +
      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
      '2. You might be breaking the Rules of Hooks\n' +
      '3. You might have more than one copy of React in the same app\n' +
      'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
  );
  return dispatcher;
}
复制代码

ReactCurrentDispatcher

源代码:github.com/facebook/re…

//导入Dispatcher类型
import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';
//跟踪当前的dispatcher.
const ReactCurrentDispatcher = {
  current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
复制代码

type Dispatcher

源代码:github.com/facebook/re…

useState

在type Dispatcher中搜索useState,往下找,会找到mountState和updateState

mountState

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  //该函数会生成一个新的hook节点,并放到节点链表的最后返回
  const hook = mountWorkInProgressHook();
复制代码

mountWorkInProgressHook

作用:获取当前的hook节点,同时将当前的hook添加到列表中,形成链表结构

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    //当前状态【reducer update 返回的第一个参数就是该属性】,用于存储更新后的state
    memoizedState: null,
    //上一次更新后的state值,与优先级有关,当dispatch传入的是一个函数的时候,这个值就是函数执行时传入的参数
    baseState: null,
    //上一次更新生成的Update结构,作用是找到本次rerender的第一个为处理的Update节点(baseUpdate.next)
    baseUpdate: null,
    //更新队列
    queue: null,
    //下一个hook
    next: null,
  };

  //该链表不只存放usestate,还会存放useeffect等相关hook
  if (workInProgressHook === null) {
    // 作为链表的头结点
    // 可以看出整个链表保存在currentlyRenderingFiber.memoizedState
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 放在链表最后一位,把workInProgressHook指向最新的节点
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
复制代码

返回继续看mouteState的代码:

  //之前看过的代码
  const hook = mountWorkInProgressHook();
  //从这里开始看
  //如果是函数,就执行得到初始state的值
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  //存储state值
  hook.memoizedState = hook.baseState = initialState;
  //创建queue节点,存放set更新函数
  const queue = (hook.queue = {...
复制代码

关于queue

作用:存放set更新函数

  //创建queue节点,存放set更新函数
  const queue = (hook.queue = {
    //最近一个等待执行的更新(update对象,下面会给出)
    pending: null,
    //setxxx方法
    dispatch: null,
    //最近一次渲染的时候用的reducer
    lastRenderedReducer: basicStateReducer,
    //最近一次渲染的时候用的state
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
复制代码

dispatchAction

作用:当setxxx的时候,创建新的update对象保存到queue中。且update对象是一个循环链表。

其他作用:

  1. 发现要重渲染的时候,会建立queue和update的映射关系,重渲染阶段会拿出来执行。
  2. 快捷操作:用当前的action先计算出一个state保存起来,如果发现这次计算的和上次计算的一样,就直接不用渲染。如果不一样,就相当于先算了一次,如果reducer在渲染阶段之前还没发生改变,就可以直接使用state而不需要再次计算。
function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 如果重渲染的次数太多,或者是参数错误就抛出,这里省略这些代码
  // ...
  const alternate = fiber.alternate;
  // 如果是渲染阶段的更新,
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 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.
    // 这是渲染阶段的更新,也就是说发生了重渲染。先把queue和update的映射关系放在一个映射表中,在这次渲染结束之后,在重渲染阶段,就会把这些update拿出来遍历执行。
    didScheduleRenderPhaseUpdate = true;
    // 为当前更新新建一个update对象
    const update: Update<S, A> = {
      expirationTime: renderExpirationTime,
      suspenseConfig: null,
      action,
      eagerReducer: null,
      eagerState: null,
      next: null,
    };
    //获取更新优先级,源码在后面给出
    if (__DEV__) {
      update.priority = getCurrentPriorityLevel();
    }
    //如果还没有renderPhaseUpdates映射就新建一个,存放当前queue节点和update的映射关系,在重渲染阶段使用
    if (renderPhaseUpdates === null) {
      renderPhaseUpdates = new Map();
    }
    const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    //如果没有对应的queue的更新,就建立对应的映射
    if (firstRenderPhaseUpdate === undefined) {
      renderPhaseUpdates.set(queue, update);
    } else {
      // Append the update to the end of the list.
      // 把更新添加到链表的最后一个
      let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
      while (lastRenderPhaseUpdate.next !== null) {
        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
      }
      lastRenderPhaseUpdate.next = update;
    }
  } else {
    //获取当前时间
    const currentTime = requestCurrentTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    //获取过期时间
    const expirationTime = computeExpirationForFiber(
      currentTime,
      fiber,
      suspenseConfig,
    );
    // 为当前更新新建一个update对象
    const update: Update<S, A> = {
      expirationTime,
      suspenseConfig,
      action,
      eagerReducer: null,
      eagerState: null,
      next: null,
    };
    //获取更新优先级
    if (__DEV__) {
      update.priority = getCurrentPriorityLevel();
    }

    // Append the update to the end of the list.
    // 把update节点添加到列表最后一位
    const last = queue.last;
    //如果queue的上一次更新中没有update,说明还没有update,就把当前的update作为第一个节点
    if (last === null) {
      // This is the first update. Create a circular list.
      // 把下一个节点指向自己,创建一个循环链表
      update.next = update;
    } else {
      //如果有update,拿到头结点
      const first = last.next;
      if (first !== null) {
        // Still circular.
        // 如果有头结点就把update的下一个节点指向头结点
        update.next = first;
      }
      //把update作为lastupdate的下一个节点,形成循环链表
      last.next = update;
    }
    //更新队列中的上一次更新列表
    queue.last = update;

    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.
      // queue当前是空的,所以我们可以在进入渲染阶段之前计算下一个状态,如果下一个状态和当前状态相同,就可以退出。
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        if (__DEV__) {
          prevDispatcher = ReactCurrentDispatcher.current;
          ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
        }
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const 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.
          // 计算state,保存上次的reducer和用它计算出来的state,如果reducer在渲染阶段之前还没发生改变,就可以直接使用state而不需要再调用reducer去计算
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(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.
            // 快捷路径(不需要重新渲染),如果组件由于不同的原因需要被重新渲染,那时候的reducer已经被改变了,我们以后可能还是要重新创建更新
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } finally {
          if (__DEV__) {
            ReactCurrentDispatcher.current = prevDispatcher;
          }
        }
      }
    }
    //...省略一些警告信息
    //实际上是调用了scheduleUpdateOnFiber,对任务进行调度
    scheduleWork(fiber, expirationTime);
  }
}
复制代码

updateState

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  //返回updateReducer
  return updateReducer(basicStateReducer, (initialState: any));
}
复制代码

basicStateReducer

作用:计算action。

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}
复制代码

参考我们平时的调用方式,当setxxx(‘管你传的啥反正不是函数’)的时候,直接就返回参数,如果setxxx(()=>{}),会把上一次的state传入,然后执行,再把返回值作为basicStateReducer的返回值。

updateReducer

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
   // 获取正在执行的处于更新阶段 Hook 节点
  const hook = updateWorkInProgressHook();
复制代码

updateWorkInProgressHook

作用:更新或生成nexthook,并保存到workInProgressHook中。

function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base. When we reach the end of the base list, we must switch to
  // the dispatcher used for mounts.
  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    // 更新下一个hook
    // 如果已经有正处于更新阶段的hook了,就使用该hook获取下一个hook
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    // 当前hook
    currentHook = nextCurrentHook;
    nextCurrentHook = currentHook !== null ? currentHook.next : null;
  } else {
    // Clone from the current hook.
    invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
    // 以currenthook为基础生成一个新的hook
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      queue: currentHook.queue,
      baseUpdate: currentHook.baseUpdate,

      next: null,
    };
    // 把newHook生成循环链表或插入到链表中
    if (workInProgressHook === null) {
      // This is the first hook in the list.
      workInProgressHook = firstWorkInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
    nextCurrentHook = currentHook.next;
  }
  return workInProgressHook;
}
复制代码

返回updateReducer:

  // 上次看到这里
  const hook = updateWorkInProgressHook();
  // 从这里开始看
  // 获取更新队列链表
  const queue = hook.queue;
  // 如果更新队列链表为空,报错
  invariant(
    queue !== null,
    'Should have a queue. This is likely a bug in React. Please file an issue.',
  );
  //保存这一次的reducer,其实就是保存了这一次更新的state
  queue.lastRenderedReducer = reducer;
  console.log(numberOfReRenders)  //一般不需要重新渲染
  if (numberOfReRenders > 0) {...}
复制代码

numberOfReRenders

numberOfReRenders会计算重新渲染的次数,他的更新在方法renderWithHooks中,renderWithHooks会判断在render阶段是否需要更新state,也就是didScheduleRenderPhaseUpdate属性,如果该属性为true,说明在render阶段发生了重渲染,如果需要重新渲染,就会计数。

export function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  props: any,
  refOrContext: any,
  nextRenderExpirationTime: ExpirationTime,
): any {
  ...
  //ʚ是否需要在 render 阶段更新 state,说明在render阶段发生了重渲染
  if (didScheduleRenderPhaseUpdate) {
    do {
      didScheduleRenderPhaseUpdate = false;
      //ʚ如果需要重渲染,就计数
      numberOfReRenders += 1;
    ...    
    }
...
}
复制代码

仔细找一下会发现,在方法dispatchAction中提示,如果numberOfReRenders < RE_RENDER_LIMIT(25次),会报错:Too many re-renders.。场景如下:

function App() {
  const [a, seta] = useState(0)
  useEffect(() => {
    console.log("useEffect")
  })
  return (
    <div className="App" onClick={seta(2)}>{a}</div>
  );
}
复制代码

输出numberOfReRenders的结果如图image-20201223222152390

返回updateReducer的代码:

如果需要重新渲染

  // 刚才看过的代码
  if (numberOfReRenders > 0) {
    // 从这里开始看
    // This is a re-render. Apply the new render phase updates to the previous
    // work-in-progress hook.
    // 获取更新队列链表里面的dispatch
    const dispatch: Dispatch<A> = (queue.dispatch: any);
    // renderPhaseUpdates就是我们在dispatchAction阶段看到的那个map
    if (renderPhaseUpdates !== null) {
复制代码

如果有renderPhaseUpdates

作用:把map中保存的更新拿出来,遍历更新。

    if (renderPhaseUpdates !== null) {
      // Render phase updates are stored in a map of queue -> linked list
      //  获取到对应的队列里面的update对象
      const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
      if (firstRenderPhaseUpdate !== undefined) {
        renderPhaseUpdates.delete(queue);
        // 获取当前的状态
        let newState = hook.memoizedState;
        // 赋值update
        let update = firstRenderPhaseUpdate;
        do {
          // 循环遍历并执行,用newState去接住更新的状态
          const action = update.action;
          newState = reducer(newState, action);
          update = update.next;
        } while (update !== null);

        // Mark that the fiber performed work, but only if the new state is
        // different from the current state.
        // 如果这次得到的state和上次得到的不一样,就标记节点更新
        if (!is(newState, hook.memoizedState)) {
          //赋值操作,源码会在后面给出
          markWorkInProgressReceivedUpdate();
        }
        // 更新当前state
        hook.memoizedState = newState;
        // Don't persist the state accumulated from the render phase updates to
        // the base state unless the queue is empty.
        // queue.last,最新一次的Update,里面包含了最新的state,在dispatch方法执行的时候挂载到queue上的
        // baseState,上一次更新后的state值,当dispatch传入的是一个函数的时候,这个值就是函数执行时传入的参数
        // baseUpdate,上一次更新生成的Update结构,作用是找到本次rerender的第一个为处理的Update节点(baseUpdate.next)
        // 除非上面循环中的update队列为空,否则不需要把渲染阶段累积的状态更新到basestate中,也就是说等到update完了,才赋值~
        if (hook.baseUpdate === queue.last) {
          hook.baseState = newState;
        }
        // 记录这一次渲染的state
        queue.lastRenderedState = newState;

        return [newState, dispatch];
      }
    }
    return [hook.memoizedState, dispatch];
  }
复制代码
hook.baseState = newState的作用

测试:代码,为了模拟一次rerender

function App() {
  const [a, seta] = useState(0)
  
  return (
    <div className="App" onClick={() => {
      console.log("点击了一次")
      seta(function (ii) {
        console.log('ii', ii)
        return ++i
      })
    }}>{i}{i === 1 && seta(++i)}{i === 2 && seta(++i)}</div> //重渲染两次
  );
}
复制代码
  1. 保留hook.baseState = newState

    image-20201231134609247

  2. 注释掉hook.baseState = newState

    image-20201231134538446

可以看出传到回调函数里的参数ii出现了问题。

如果没有发生重渲染

继续走代码,以下就是没有发生重渲染的情况:

作用:把queue.last中保存的update拿出来,展开变成普通链表,遍历更新。

  //...刚才看过的代码
    return [hook.memoizedState, dispatch];
  }
  // 从这里开始看
  // The last update in the entire queue
  // 最后一次的update对象
  // 为什么这里是null
  const last = queue.last;
  // The last update that is part of the base state.
  // 上一轮更新的最后一次更新对象
  const baseUpdate = hook.baseUpdate;
  // 上一次的action,现在是初始值
  const baseState = hook.baseState;
  // console.log('baseState', baseState)

  // Find the first unprocessed update.
  let first;
  // console.log(baseUpdate !== null) ʚ第一次是null,update的时候会被赋值
  if (baseUpdate !== null) {
    // 这里是后面的update
    if (last !== null) {
      // For the first update, the queue is a circular linked list where
      // `queue.last.next = queue.first`. Once the first update commits, and
      // the `baseUpdate` is no longer empty, we can unravel the list.
      // 展开链表,不需要使用循环链表了
      last.next = null;
    }
    first = baseUpdate.next;  //后面的循环链表都是基于baseUpdate
  } else {
    // 这里是第一个update,是循环链表,为了得到第一个更新,使用了last.next。
    first = last !== null ? last.next : null;
  }
复制代码

对于first,有如下代码:

function App() {
  const [a, seta] = useState(0)
  return (
    <div className="App" onClick={() => {
      seta(1)
      seta(2)
    }}>{i}</div>
  );
}
复制代码
  1. 第一次点击,得到的链表是1-2-1-2-1-2..
  2. 第二次点击,得到的链表是1-2-null

为什么要做成循环链表之后又展开

因为update是一个一个连上去的,最后一个update就是最新的update,做成循环链表是为了在updateReducer阶段更容易获取到头结点,解开是因为updateReducer的时候要一个一个把update拿出来进行更新,直到next为null才算更新完毕。

关于优先级

回到updateReducer继续往下走,可以看到优先级的判断:

    // 刚才看过的代码
    first = last !== null ? last.next : null;
  }
  //从这里开始看
  if (first !== null) {
    let newState = baseState;
    let newBaseState = null;
    let newBaseUpdate = null;
    let prevUpdate = baseUpdate;
    let update = first;
    let didSkip = false;
    do {
      const updateExpirationTime = update.expirationTime;
      if (updateExpirationTime < renderExpirationTime) {
复制代码

ExpirationTime

为防止某个update因为优先级的原因一直被打断而未能执行。React会设置一个ExpirationTime,当时间到了ExpirationTime的时候,如果某个update还未执行的话,React将会强制执行该update

这里的代码不是我一字一句解读的了,大部分都是从博客看来的,主要参考文章:zhuanlan.zhihu.com/p/108274604…

从updateContainer方法开始看:

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  const current = container.current;
  const currentTime = requestCurrentTime();
复制代码
updateContainer-requestCurrentTime

作用:获取currentEventTime,根据不同情况返回当前时间或不做更新。

export function requestCurrentTime() {
  //executionContext 的初始值为NoContext,即0b000000
  //这里是判断executionContext是否处于RenderContext(计算更新)或CommitContext(提交更新)阶段
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // We're inside React, so it's fine to read the actual time.
    // 该阶段直接获取当前时间
    return msToExpirationTime(now());
  }
  // We're not inside React, so we may be in the middle of a browser event.
  // 这里我的理解和参考文章有出入,我的理解是:不处于noWork说明在处理浏览器事件,先直接使用开始的时间,等到再次工作再赋值?
  if (currentEventTime !== NoWork) {
    // Use the same start time for all updates until we enter React again.
    return currentEventTime;
  }
  // This is the first update since React yielded. Compute a new start time.
  // 第一次更新,初始化一个当前时间
  currentEventTime = msToExpirationTime(now());
  return currentEventTime;
}
复制代码
msToExpirationTime

作用:以10ms为单位,对毫秒进行向上取整。

//1073741823
export const Sync = MAX_SIGNED_31_BIT_INT;
//1073741822
export const Batched = Sync - 1;

const UNIT_SIZE = 10;
//1073741821
const MAGIC_NUMBER_OFFSET = Batched - 1;

// 1 unit of expiration time represents 10ms.
// 10毫秒为1单位,有利于批量更新
export function msToExpirationTime(ms: number): ExpirationTime {
  // Always add an offset so that we don't clash with the magic number for NoWork.
  // 1073741821-((ms/10) | 0)
  // |0表示向下取整,再加1,即向上取整
  return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}
复制代码

返回updateContainer,继续看代码

  const currentTime = requestCurrentTime();
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );
复制代码
updateContainer-computeExpirationForFiber

作用:计算过期时间

export function computeExpirationForFiber(
  currentTime: ExpirationTime,
  fiber: Fiber,
  suspenseConfig: null | SuspenseConfig,
): ExpirationTime {
  const mode = fiber.mode;
  //是否是 NoMode,如果是,那就是同步渲染机制
  if ((mode & BatchedMode) === NoMode) {
    return Sync;
  }
  //获取优先级,源码在后面给出
  const priorityLevel = getCurrentPriorityLevel();
  //判断是否是 BatchedMode
  if ((mode & ConcurrentMode) === NoMode) {
    return priorityLevel === ImmediatePriority ? Sync : Batched;
  }
  //处理 Concurrent Mode,该情况会返回之前的renderExpirationTime
  if ((executionContext & RenderContext) !== NoContext) {
    // Use whatever time we're already rendering
    // TODO: Should there be a way to opt out, like with `runWithPriority`?
    return renderExpirationTime;
  }

  let expirationTime;
  if (suspenseConfig !== null) {
    // Compute an expiration time based on the Suspense timeout.
    // 计算Suspense的过期时间
    expirationTime = computeSuspenseExpiration(
      currentTime,
      suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
    );
  } else {
    // Compute an expiration time based on the Scheduler priority.
    // 根据优先级计算过期时间
    switch (priorityLevel) {
      case ImmediatePriority:
        // 立即执行,MAX_SIGNED_31_BIT_INT
        expirationTime = Sync;
        break;
      case UserBlockingPriority:
        // TODO: Rename this to computeUserBlockingExpiration
        // 计算交互事件的过期时间,源码在后面给出
        expirationTime = computeInteractiveExpiration(currentTime);
        break;
      case NormalPriority:
      case LowPriority: // TODO: Handle LowPriority
        // TODO: Rename this to... something better.
        // 计算异步更新的过期时间,源码在后面给出
        expirationTime = computeAsyncExpiration(currentTime);
        break;
      case IdlePriority:
        // 1
        expirationTime = Idle;
        break;
      default:
        invariant(false, 'Expected a valid priority level');
    }
  }

  // If we're in the middle of rendering a tree, do not update at the same
  // expiration time that is already rendering.
  // TODO: We shouldn't have to do this if the update is on a different root.
  // Refactor computeExpirationForFiber + scheduleUpdate so we have access to
  // the root when we check for this condition.
  // 如果正在渲染tree,就不要和已经在渲染的tree在同一时间更新
  // 因此降低优先级
  if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
    // This is a trick to move this update into a separate batch
    expirationTime -= 1;
  }

  return expirationTime;
}
复制代码
getCurrentPriorityLevel

作用:获取优先级

export function getCurrentPriorityLevel(): ReactPriorityLevel {
  // Scheduler_getCurrentPriorityLevel其实是return currentPriorityLevel;
  // D:\react\ReactSourceCodeAnalyze\source-code-demo\src\react\packages\scheduler\src\Scheduler.js
  // 简单看了一下代码,currentPriorityLevel一开始是NormalPriority,但是可能会被赋值为节点的priorityLevel,如果使用priorityLevel时运行出错,就会使用回上一个priorityLevel。
  switch (Scheduler_getCurrentPriorityLevel()) {
    //export const unstable_ImmediatePriority = 1;
    case Scheduler_ImmediatePriority:
      return ImmediatePriority;
    //export const unstable_UserBlockingPriority = 2;
    case Scheduler_UserBlockingPriority:
      return UserBlockingPriority;
    //export const unstable_NormalPriority = 3;
    case Scheduler_NormalPriority:
      return NormalPriority;
    //export const unstable_LowPriority = 4;
    case Scheduler_LowPriority:
      return LowPriority;
    //export const unstable_IdlePriority = 5;
    case Scheduler_IdlePriority:
      return IdlePriority;
    default:
      invariant(false, 'Unknown priority level.');
  }
}
复制代码
computeInteractiveExpiration和computeAsyncExpiration

作用:计算同步和异步的过期时间

// We intentionally set a higher expiration time for interactive updates in
// dev than in production.
// 我们有意为开发环境的交互式更新设置了比生产环境更长的到期时间
// If the main thread is being blocked so long that you hit the expiration,
// it's a problem that could be solved with better scheduling.
// 如果主线程被阻塞的时间太长导致更新到期,这个问题可以通过更好的调度解决
// People will be more likely to notice this and fix it with the long
// expiration time in development.
// 人们更有可能注意到这一点,并在开发过程中使用较长的过期时间来修复它。
// In production we opt for better UX at the risk of masking scheduling
// problems, by expiring fast.
// 在生产环境下,我们选择了更好的用户体验,通过快速过期来掩盖调度问题的风险。
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,   //500
    HIGH_PRIORITY_BATCH_SIZE,   //100
  );
}

// TODO: This corresponds to Scheduler's NormalPriority, not LowPriority. Update
// the names to reflect.
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;

export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,   //5000
    LOW_PRIORITY_BATCH_SIZE,   //250
  );
}
复制代码
computeExpirationBucket
// (num / precision) | 0)是向下取整,+1就是向上取整
function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  // 如果是低优先级:1073741821-ceiling(1073741821-currentTime+500,25)
  // 1073741821-((((1073741821-currentTime+500) / 25) | 0) + 1) * 25
  // 大概思路就是/25之后抹平小数点再*25,也就是说一定要是25的倍数,方便批量更新
  // 如果是高优先级:1073741821-ceiling(1073741821-currentTime+50,10)
  // 一定要是10的倍数
  return (
    MAGIC_NUMBER_OFFSET -
    ceiling(
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}
复制代码

updateContainer后面的代码大概是根据生成的过期时间来生成更新和调度,下次看fiber的时候再详细看吧,再看就偏离主题了呜呜呜,差点忘了我是在学习useState…

继续看没有发生重渲染

作用:如果有优先级小的情况,就直接赋值为上一轮循环得到的结果,因为他现在还不需要被执行,等到被执行的时候,要保证在这基础上再执行。如果该更新的优先级足够,就拿出来循环更新。所有更新的优先级都足够,就赋值最新的update和最新的state。

如果最后得到的状态不同,就标记更新。

最后,更新该hook。

    //刚才看过的代码
    do {
      const updateExpirationTime = update.expirationTime;
      //从这里开始看
      if (updateExpirationTime < renderExpirationTime) {
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        // 如果优先级小,
        if (!didSkip) {
          didSkip = true;
          //直接设置update和newstate,跳过之后的赋值行为
          //其实就是上一"轮"循环中得到的prevUpdate和newState
          //如果第一次循环就走到这里,那么得到的就是之前的prevUpdate和newState
          //因为在他被执行得时候,需要保证后续的更新要在他更新之后的基础上再次执行,因为结果可能会不一样。
          newBaseUpdate = prevUpdate;
          newBaseState = newState;
        }
        // Update the remaining priority in the queue.
        if (updateExpirationTime > remainingExpirationTime) {
          remainingExpirationTime = updateExpirationTime;
          markUnprocessedUpdateTime(remainingExpirationTime);
        }
      } else {
        // This update does have sufficient priority.
        // 此次更新有足够的优先级
        // Mark the event time of this update as relevant to this render pass.
        // 更新的最近一次处理的事件的过期时间,会更新一个叫做workInProgressRootLatestProcessedExpirationTime的属性,作用好像是判断有没有处理新的更新(如果在渲染过程中有新的更新,可能会有新的加载状态,要尽快提交),和推断suspense更新相关的时间。
        // TODO: This should ideally use the true event time of this update rather than
        // its priority which is a derived and not reverseable value.
        // TODO: We should skip this update if it was already committed but currently
        // we have no way of detecting the difference between a committed and suspended
        // update here.
        markRenderEventTimeAndConfig(
          updateExpirationTime,
          update.suspenseConfig,
        );

        // Process this update.
        // 处理这一次更新
        if (update.eagerReducer === reducer) {
          // If this update was processed eagerly, and its reducer matches the
          // current reducer, we can use the eagerly computed state.
          // 如果这一次更新是实时的,而且reducer也和当前的reducer相匹配,就直接使用实时的状态。
          // eagerState,用处是:如果reducer在进入渲染状态的时候还没有发生改变的话,就可以直接使用eagerState而不需要再次计算。
          newState = ((update.eagerState: any): S);
        } else {
          // 否则就重新计算状态
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      //更新update为下一个update
      prevUpdate = update;
      update = update.next;
    } while (update !== null && update !== first);
    // 如果所有更新的优先级都足够,就赋值为最新的update和newState
    if (!didSkip) {
      newBaseUpdate = prevUpdate;
      newBaseState = newState;
    }

    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    // 只有这次的状态和之前的状态不同的时候,才会标记更新完成
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }
    // 更新为新的状态和update
    hook.memoizedState = newState;
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState;
    queue.lastRenderedState = newState;
  }
  
  //返回state和useState
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
复制代码

memoizedState和baseState的区别

hook.memoizedState被赋值:mountState中的initialstate,updateReducer中的newState。

hook.baseState被赋值:mountState中的initialstate,updateReducer中的newBaseState,而newBaseState会在优先级低的时候赋值为之前的newState而不是最新的newState,在hook.baseUpdate === queue.last时会被直接赋值为newState。

hook.baseUpdate的情况和hook.baseState一致。

也就是说在每一次的循环中都分两种情况:

  1. 如果优先级低

    说明应该不更新,newBaseState会被赋值为当次更新时得到的newState。因为之后优先级足够时,要在baseState的基础上更新。

  2. 如果优先级高

    应该更新,正常赋值newState。等到更新结束之后,把最新的newState赋值给newBaseState。

其他函数源码

invariant:抛出错误

export default function invariant(condition, format, a, b, c, d, e, f) {
  validateFormat(format);
 
  if (!condition) {
    let error;
    if (format === undefined) {
      error = new Error(
        'Minified exception occurred; use the non-minified dev environment ' +
          'for the full error message and additional helpful warnings.',
      );
    } else {
      const args = [a, b, c, d, e, f];
      let argIndex = 0;
      error = new Error(
        format.replace(/%s/g, function() {
          return args[argIndex++];
        }),
      );
      error.name = 'Invariant Violation';
    }
 
    error.framesToPop = 1; // we don't care about invariant's own frame
    throw error;
  }
}
复制代码

mark标记相关函数

export function markWorkInProgressReceivedUpdate() {
  //标记节点更新已完成
  didReceiveUpdate = true;
}
复制代码

一些平常遇到的问题

使用prop作为参数,然后异步改变prop

function Child(prop){
  const [b, setb] = useState(`c${prop.b}`)
  return <p>{b}</p>
}

function App() {
  const [a, seta] = useState(0)
  useEffect(() => {
    setTimeout(()=>{
      seta(10)
    }, 1000)
  })

  return <Child b={a}/>
}
复制代码

嘿,你猜怎么着,最终页面上显示的是0。把整个state的流程跑一遍看看:

一开始走mountState的时候:

  • 父组件

    1. 通过mountWorkInProgressHook初始化一个hook并且挂到hook队列【firstWorkInProgressHook】上

    2. 通过传入的参数知道initialstate为0,并且挂到hook的memoizedState属性上返回

    3. 初始化一个queue

        const queue = (hook.queue = {
          last: null,
          dispatch: null,
          lastRenderedReducer: basicStateReducer,  //计算,如果acrion是数值就返回action,如果是函数就返回action(state)
          lastRenderedState: (initialState: any),  //初始值,0
        });
      复制代码
    4. 返回[memoizedState,dispatchAction(diber, queue)],调用setxxx(xxx)时,为dispatchAction传入action值。

运行到return <Child />这里,开始运行子组件的mountState。

  • 子组件

    工作流程同父组件,只有initialState不同,为c0。

    baseState: "c0"
    baseUpdate: null
    memoizedState: "c0"
    next: null
    queue:
        dispatch: ƒ ()
        last: null
        lastRenderedReducer: ƒ basicStateReducer(state, action)
        lastRenderedState: "c0"
    复制代码

运行到<p>{b}</p>,渲染完成,页面上显示c0。

接下来运行useEffect里面的setTimeout,运行seta(10)。于是直接运行dispatchAction函数。该函数初始化了一个update对象,存入action,过期时间,优先级等属性,然后用update.next生成一个循环链表,保存到queue.last中。

const update: Update<S, A> = {
  expirationTime,
  suspenseConfig,
  action,
  eagerReducer: null,
  eagerState: null,
  next: null,
};
复制代码

然后先在update.eagerReducer保存一下当时的lastRenderedReducer,和在update.eagerState保存当时的lastRenderedReducer计算出来的state(10)。比较计算的state和上次渲染的state(0),发现不同,于是开始进行调度工作,重新渲染组件。

进入updateReducer之后:

  • 父组件

    1. 先执行的是const [a, seta] = useState(0),进入updateReducer。

    2. 执行updateWorkInProgressHook,可以看到当时的currentHook是null,而nextCurrentHook.queue.action=10(dispatchAction搞的)。

    3. 由于没有nextWorkInProgressHook,于是先把nextCurrentHook赋值给currentHook,再把currentHook的内容复制到newHook上,

      const newHook: Hook = {
        memoizedState: currentHook.memoizedState,
      
        baseState: currentHook.baseState,
        queue: currentHook.queue,
        baseUpdate: currentHook.baseUpdate,
      
        next: null,
      };
      复制代码

      赋值给workInProgressHook之后形成链表结构,最后抛出该hook。

    4. updateReducer函数获取到hook之后,得到里面保存的queue,queue的代码如下:

      baseState: 0
      baseUpdate: null
      memoizedState: 0
      next: null
      queue:
          dispatch: ƒ ()
          last:
              action: 10
              eagerReducer: ƒ basicStateReducer(state, action)
              eagerState: 10
              expirationTime: 1073741823
              //循环链表结构
              next: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerState: 10, eagerReducer: ƒ, …}
              priority: 97
              suspenseConfig: null
      lastRenderedReducer: ƒ basicStateReducer(state, action)
      lastRenderedState: 0
      复制代码
    5. 由于没有发生重渲染,所以直接走不重渲染的部分。用last.next找到头结点(第一个update),然后把update解开形成单链表形式。

    6. last.next=first=update,只要有update.next就一直循环,一直用reducer(newState, action)得到newState(10)。

    7. 最后得到的newState(10)和hook中上一次记录的hook.memoizedState(0)不同,所以需要重新渲染。标记markWorkInProgressReceivedUpdate()。

    8. 最后更新hook的state和update,更新queue的lastRenderedState。

    接下来执行到useEffect,这个时候还没进行seta(10)。

    1. 会经过updateWorkInProgressHook ,当前的currentHook是:

      复制代码

    baseState: 0
    baseUpdate: null
    memoizedState: 0
    next:
    baseState: null
    baseUpdate: null
    memoizedState: {tag: 192, destroy: undefined, deps: null, next: {…}, create: ƒ}
    next: null
    queue: null
    queue:
    dispatch: ƒ ()
    last: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerState: 10, eagerReducer: ƒ, …}
    lastRenderedReducer: ƒ basicStateReducer(state, action)
    lastRenderedState: 10

    
    有nextCurrentHook赋值形成的currentHook是:
    
    ```js
    baseState: null
    baseUpdate: null
       memoizedState:
       create: () => {…}
       deps: null
       destroy: undefined
       next: {tag: 192, destroy: undefined, deps: null, next: {…}, create: ƒ}
       tag: 192
       __proto__: Object
       next: null
       queue: null
       __proto__: Object
    复制代码

    赋值给到workInProgressHook并放到链表最后,返回给updateEffect函数,之前的workInProgressHook如下。

    baseState: 10
    baseUpdate:
       action: 10
       eagerReducer: ƒ basicStateReducer(state, action)
       eagerState: 10
       expirationTime: 1073741823
       next: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerState: 10, eagerReducer: ƒ, …}
       priority: 97
       suspenseConfig: null
       __proto__: Object
       memoizedState: 10
       next: null
       queue:
       dispatch: ƒ ()
       last: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerState: 10, eagerReducer: ƒ, …}
       lastRenderedReducer: ƒ basicStateReducer(state, action)
       lastRenderedState: 10
       __proto__: Object
       __proto__: Object
    复制代码

    然后运行到<Child/>,就进去renderWithHooks,又进到子组件的updateReducer,判断是否需要重新渲染。

    1. 经过updateWorkInProgressHook,此时currentHook为null,之后得到currentHook=nextCurrentHook:

      baseState: "c0"  //子组件上一次渲染时的值,也就是mount阶段生成的hook
      baseUpdate: null
      memoizedState: "c0"
      next: null
      queue:
          dispatch: ƒ ()
          last: null
          lastRenderedReducer: ƒ basicStateReducer(state, action)
          lastRenderedState: "c0"
          __proto__: Object
      __proto__: Object
      复制代码

      复制给newHook之后,放到了链表的最后。

    2. 这个时候,last=null, baseupdate=null,所以first也只能为null。

      first = last !== null ? last.next : null;

      因此后面的步骤全都跳过,直接来到return [hook.memoizedState, dispatch],即return[‘c0’, dispatch]

    所以说,异步问题的直接原因是updateWorkInProgressHook没有得到更新,再次渲染的时候,其next还是null,导致直接跳过update阶段。

    第二次运行组件的代码的时候,其实不会再直接用useState中传入的初始值去得到action(有作为参数传入updateReducer但不会被应用),而是直接看钩子中有没有新的update,所以我们需要重新setxxx一下。但是直接setxxx会造成死循环,所以可以搞个useEffect[],只当prop变化之后更新一次。

  • 父组件

    最后隔个1000秒,又是父组件的更新操作seta(10),(因为刚才seta(10)之后他又重新跑了一遍父组件的代码),然后又经过了useState(0)的那一步等等等等,不是重点所以就不重复说了,最后得到的hook如下:

    baseState: 10
    baseUpdate:
    action: 10
    eagerReducer: ƒ basicStateReducer(state, action)
    eagerState: 10
    expirationTime: 1073741823
    next: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerReducer: null, eagerState: null, …}
    priority: 97
    suspenseConfig: null
    __proto__: Object
    memoizedState: 10
    next: null
    queue:
    dispatch: ƒ ()
    last: {expirationTime: 1073741823, suspenseConfig: null, action: 10, eagerReducer: null, eagerState: null, …}
    lastRenderedReducer: ƒ basicStateReducer(state, action)
    lastRenderedState: 10
    __proto__: Object
    __proto__: Object
    复制代码

    发现和之前的state值一样,所以不做更新。

react优先级:xiaoxiaosaohuo.github.io/react-books…

segmentfault.com/a/119000002…

segmentfault.com/a/119000002…

www.zyiz.net/tech/detail…

www.cnblogs.com/zhongmeizhi…

www.sohu.com/a/402114001…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享