React 的源码与原理解读(十四):Hooks解读之三 useState&useReducer

写在专栏开头(叠甲)

  1. 作者并不是前端技术专家,也只是一名喜欢学习新东西的前端技术小白,想要学习源码只是为了应付急转直下的前端行情和找工作的需要,这篇专栏是作者学习的过程中自己的思考和体会,也有很多参考其他教程的部分,如果存在错误或者问题,欢迎向作者指出,作者保证内容 100% 正确,请不要将本专栏作为参考答案。

  2. 本专栏的阅读需要你具有一定的 React 基础、 JavaScript 基础和前端工程化的基础,作者并不会讲解很多基础的知识点,例如:babel 是什么,jsx 的语法是什么,需要时请自行查阅相关资料。

  3. 本专栏很多部分参考了大量其他教程,若有雷同,那是作者抄袭他们的,所以本教程完全开源,你可以当成作者对各类教程进行了整合、总结并加入了自己的理解。

本一节的内容

这个章节主要讲解React 的 useState 和 useReducer 这两个 api,这是我们使用最多的 hook 了,他们允许我们在函数组件中也持有状态 state,然后对这个状态进行操作,这一章我们会从使用和源码两个角度深入讲解这两个 hooks

useState 的定义

useState()是我们最常见的几个 hooks 之一,它运行我们传入一个初始值来初始化一个 state,之后返回给我们这个 state 和改变它的方法 setState:

const [state, setState] = useState(initialstate)

这里我们来看一个面试题:我们使用一个数组来接收 useState 的赋值,这是因为其使用的是 es6 的解构赋值,数组解构时变量的取值由数组元素的位置决定,变量名可以任意命名,这样的设计使得我们可以自定义 useState 的返回值,比如:

const [num, setNum] = useState(0)

useState 的使用

基本用法

对于 useState 的使用相信大部分的 React 使用者都了然于心,这里就不多介绍了,一个很简单的计时器的例子带过:

function Counter() {
    
    
  const [count, setCount] = useState(0);
  function handleClick() {
    
    
    setCount(count + 1)
  }
  return (
    <>
      Count: {
    
    count}
      <button onClick={
    
    handleClick}>+</button>
    </>
  );
}

函数式更新

useState 也提供了函数式的更新,我们可以将之前的数值作为参数传入,得到更新后的结果:

function Counter() {
    
    
  const [count, setCount] = useState(0);
  function handleClickFn() {
    
    
    setCount((prevCount) => {
    
    
      return prevCount + 1
    })
  }
  return (
    <>
      Count: {
    
    count}
      <button onClick={
    
    handleClickFn}>+</button>
    </>
  );
}

两者的差异

这两种更新方法的区别是:函数式更新传入的值是当前的最新值,而前者则不然,因为 setState 是异步的,这是一个例子:

我们连续做三次 setCount 操作,前者只有一次生效,对同一次的渲染来说,count 是一个固定值,无论在哪里使用这个值,都是固定的。setCount(count+1) 的作用仅仅是把要更新的最新数据记录在了 React 内部,然后等待下次的渲染更新。

当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是每次都是最新的值。

稍后我们会结合源码来深入理解这个特性

function Counter() {
    
    
  const [count, setCount] = useState(0);
  function handleClick() {
    
    
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }
  // 1
  function handleClickFn() {
    
    
    setCount(count => count + 1);
    setCount(count => count + 1);
    setCount(count => count + 1);
  }
  // 3
  return (
    <>
      Count: {
    
    count}
      <button onClick={
    
    handleClick}>+</button>
      <button onClick={
    
    handleClickFn}>+</button>
    </>
  );
}

同步获取更新后的值

根据上面是描述,setState 只有在下次的渲染更新才会更新,所以它是更新是异步的,而我们知道,在 class 组件中,我们可以通过回调函数来获取最新的 state 的值,但是在 function 组件中呢?我们可以通过两种方法来实现

  1. 直接计算出我们需要的值

我们可以直接计算出我们需要的结果,更新这个状态,之后将我们计算出的结果提供给我们需要的函数即可

const handleClick = () => {
    
    
  const newCount = count + 1;
  setCount(newCount);
  getList(newCount);
};
  1. 使用 useEffect 来监听

我们知道 useEffect 这个钩子可以监控一个依赖项的变化,因为我们不确定 state 什么时候发生改变,所以我们可以通过监听它的变化来,直到它发生变化才进行后续的操作

function App() {
    
    
  const [count, setCount] = useState(0);

  useEffect(() => {
    
    
    getList();
  }, [count]);

  const handleClick = () => {
    
    
    setCount(count + 1);
  };
}

覆盖对象而不是合并

useState 返回的 setState 和 class 组件的 setState 的区别是对于 object 的操作:

  • class 组件中会合并你两次传入的对象
  • 但是 useState 返回的 setState 是直接覆盖原来的元素的

产生这个问题的主要原因是,hooks 内部使用了 object.is 对两次的数值进行比较,不清楚的可以查看上一篇的教程,它只会比较两个对象元素引用的地址是不是一致,不一致则直接替换

class App {
    
    
  state = {
    
    
    key1: "value1",
    ey1: "value1",,
  };
  handleClick() {
    
    
    this.setState({
    
     key2: "value3" }); 
  }
  // key1: "value1", key2: "value3",
}

function App() {
    
    
  const [Info, setInfo] = useState({
    
     ey1: "value1", key2: "value2", });
  // 手动展开再赋值
  setInfo({
    
     ...Info, key2: "value3" });
}

useState 的源码

mount 阶段

接下来我们来看 useState 的源码,它的部分逻辑和我们之前在 Lane 这一章中的是一致的,大家阅读起来应该会比较轻松,我们还是先从 mount 开始看,这里我们需要回顾一下一些概念,可以跟着我一行一行来回顾:

  • 首先是我们获取传入的初始值,如果是函数的话,我们执行它获取结果作为我们的初始值
  • 之后我们将我们的初始化放到 hook 节点的 memoizedStatebaseState 属性上,这部分在 hook 的原理这章提到了,memoizedState 代表上一次处理完成的 state,因为我们的初次挂载,所以它就是初始值,这个属性也是我们返回给用户的 state 属性。而 baseState 的当前已经处理完的更新产生的 state,他是为了在一连串的更新执行过程中记录中间状态设定的,因为是初次挂载,已经处理完的就是当前的 state。
  • 之后我们创建一个更新队列,我们将他们放在 hook 的 queue 属性中,其中 lastRenderedReducer 属性绑定我们上一次在 setState 函数传入的内容,可能是数值或者一个函数;而 lastRenderedState 属性存放我们上次render后的state
  • 最后我们使用 dispatchSetState 生成一个更新函数,返回给用户,这个函数被绑定在了 hook 更新的 dispatch 属性上,值得注意都是 dispatchSetState 函数本来需要传入三个参数,在使用 bind 将其中两个参数传入之后,我们只需要提供一个参数即可,而这个新生成的函数就是我们的 setState 更新函数
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
    
    
  const hook = mountWorkInProgressHook();
  // 如果传入的初始值是一个函数,直接执行获得结果
  if (typeof initialState === 'function') {
    
    
    initialState = initialState();
  }
  // 更新 hook 缓存的数据
  hook.memoizedState = hook.baseState = initialState;
  // 创建一个更新队列
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    
    
    pending: null,     // 依然是环状链表,这个属性指向链表的最后一个节点
    interleaved: null,
    lanes: NoLanes,  
    dispatch: null,    // setState
    lastRenderedReducer: basicStateReducer,    // 上次render传入的操作
    lastRenderedState: (initialState: any),    // 上次render后的state
  };
  hook.queue = queue;
  // 生成更新函数
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

之后我们来看 dispatchSetState 这个函数做了什么:

  • 首先它获取了本次更新的优先级 ,requestUpdateLane 我们在 lane 这章着重讲过,不清楚的可以回去查看
  • 之后我们将这个更新封装成一个 Update 节点,要注意,这个 Update 节点是为了我们 hooks 服务的,和之前提到的 updateQueue 那节的不一样,但是处理方式基本一致,它会被挂载在 hook 的 queue 的 pending 属性上
  • 之后判定这个更新是不是在渲染阶段发生,如果是把它拼接到 queue 的 pending 上,并且设定一个标识
  • 如果是正常更新,我们还是首先把它拼接到 queue 的 pending 上,如果当前节点没有更新任务,直接计算出新的 state,然后与之前的 state 对比,若没有更新,则直接退出;
  • 若有更新,我们调用 scheduleUpdateOnFiber 函数,这个函数在 Lane 这章节已经详细讲过了,可以回去看这个函数

总结来说就是,把我们的 setState 的更新内容(数据或者函数)放到了 hook 的queue 的pending 属性中,如果组件之前没有更新事件,直接计算出 state 的新值,值不变则不触发更新,否则新建一个更新事件开始调度

function dispatchSetState<S, A>(fiber: Fiber, queue: UpdateQueue<S, A>, action: A) {
    
    
  // 获取lane
  const lane = requestUpdateLane(fiber);

  // 将 action 操作封装成一个 update节点,用于后续构建链表使用
  const update: Update<S, A> = {
    
    
    lane,                 // 优先级
    action,               // setState 传入的内容,可能是操作或者数值
    hasEagerState: false, // 紧急状态
    eagerState: null,     // 紧急状态下提前计算出的结果
    next: (null: any),    // 指向到下一个节点的指针
  };
  // 在渲染阶段的更新,拼接到 queue.pending 的后面
  if (isRenderPhaseUpdate(fiber)) {
    
    
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    
    
	// 把 update 更新到 queue.pending 指向的环形链表里
    enqueueUpdate(fiber, queue, update, lane);
    const alternate = fiber.alternate;
    // 如果当前节点没有更新任务
    if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
    
    
      const lastRenderedReducer = queue.lastRenderedReducer; // 上次render后的reducer,在mount时即 basicStateReducer
      if (lastRenderedReducer !== null) {
    
    
        let prevDispatcher;
        const currentState: S = (queue.lastRenderedState: any); // 上次render后的state,mount时为传入的initialState
        const eagerState = lastRenderedReducer(currentState, action);
        update.hasEagerState = true; // 表示该节点的数据已计算过了
        update.eagerState = eagerState; // 存储计算出来后的数据
        if (is(eagerState, currentState)) {
    
    
          // 若这次得到的state与上次的一样,则不再重新渲染
          return;
        }
      }
    }
    // 有更新任务
    const eventTime = requestEventTime();
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (root !== null) {
    
    
      entangleTransitionUpdate(root, queue, lane);
    }
  }
  markUpdateInDevTools(fiber, lane, action);
}

function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
) {
    
    
  // 标识render阶段的更新产生了
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  const pending = queue.pending;
  if (pending === null) {
    
    
    update.next = update;
  } else {
    
    
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

function enqueueUpdate<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
  lane: Lane,
) {
    
    
  // 交错更新
  if (isInterleavedUpdate(fiber, lane)) {
    
    
    const interleaved = queue.interleaved;
    if (interleaved === null) {
    
    
      update.next = update;
      pushInterleavedQueue(queue);
    } else {
    
    
      update.next = interleaved.next;
      interleaved.next = update;
    }
    queue.interleaved = update;
  } else {
    
    
    const pending = queue.pending;
    if (pending === null) {
    
    
      update.next = update;
    } else {
    
    
      update.next = pending.next;
      pending.next = update;
    }
    queue.pending = update;
  }
}

update 阶段

之后我们来看看 update 阶段发生了什么,这里先要提一句,我们在 mount 阶段仅仅是初始化了 hooks,返回一个 setState 函数,而这个函数的作用是将传入的 action 放到 hook 的更新列表中,但是实际上的更新操作是在 update 阶段才会去执行的,这也是我们上面提到的函数式更新和数值更新产生差异的原因,我们具体来说:

我们承接上文,scheduleUpdateOnFiber 函数开始了一个调度,当它执行到了 renderWithHooks 的时候,此时因为以及渲染出了我们的 Fiber,所以会进入更新的钩子,就是 updateState 函数,而它直接调用了 updateReducer 函数,这个其实是 useReducer 这个钩子的方法,我们马上会讲到

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
    
    
  return updateReducer(basicStateReducer, (initialState: any));
}

这里我们来看一下 updateReducer 这个函数,它是我们传入 updateReducer 的逻辑,也就是我们 setState 的更新逻辑,如果我们传入一个值,那么我们直接返回这个值,如果我们传入一个函数,我们返回计算后的结果,而这个函数需要传入一个参数,也就是我们函数式更新传入的上一次的渲染值:

function basicStateReducer(state, action) {
    
    
  return typeof action === 'function' ? action(state) : action;
}

我们直接来看这个函数updateReducer,他的逻辑和我们之前的 fiber 上的 Update 的更新逻辑类似,大家应该很熟悉,我们简单来概括一下:

  • 首先获取当前的 pending 队列和上次遗留下来的队列,把他们合并到一起

  • 之后遍历队列中所有的 hook 更新,根据当前的优先级判定它能不能执行

  • 若符合当前优先级的,则执行该 update 节点的 action,计算出新的 state,它将作为我们的返回值,可以看到,这里我们传入的当前更新前计算出的最新的值,也就说,我们每次传入的值都是到这个更新触发前最新的 state,这样保证上文中的函数式更新可以每次都能正常更新,而如果我们不使用函数式更新,那么我们传入的值即使包含了 state,那这个 state 也不过是我们调用那个时刻的 state ,但是如果它之前有其他的更新就不能得到响应了;若优先级不符合的,则将此节点到最后的所有节点都存储起来,便于下次渲染遍历,并将到此刻计算出的 state 作为下次更新时的基准 state。

    **注意:**这个逻辑我们需要注意,对于我们遇到的所有可执行的任务,我们都需要执行其 action,然后更新我们执行它之后的 state;但是如果我们已经遇到一个不可执行的任务了,即使当前任务可以执行,我们也要把可执行任务放到 BaseQueue 中,那么在我们遍历了全部的任务之后,我们的 BaseQueue 中存放的是从第一个不可执行任务之后的全部任务,他们会在下一次渲染的时候执行,这个渲染需要一个 baseState,也就是我们初始化的 newBaseState 这个变量,它的值会在第一次出现不可知性的任务的时候,把执行到目前的 state 保存下来,也就是说, BaseState 是和 BaseQueue 配合使用的, 一次渲染后, BaseState 的值和渲染得到的结果不一定是相同的!只有在全部任务执行完成的情况下,他们才可能完全相同

  • 遍历完所有可以执行的任务后,得到一个新的 newState,然后判断与之前的 state 是否一样,若不一样,则标记该 fiber 节点需要更新,并返回新的 newState 和 dispatch 方法。

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
    
    
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  queue.lastRenderedReducer = reducer;
  const current: Hook = (currentHook: any);
  //上次遗留下来的优先级不够的任务
  let baseQueue = current.baseQueue;
  // 获取 queue 队列
  const pendingQueue = queue.pending;
  // 拼接链表
  if (pendingQueue !== null) {
    
    
    if (baseQueue !== null) {
    
    
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null; 
  }

  if (baseQueue !== null) {
    
    
    const first = baseQueue.next;
    let newState = current.baseState; // 上次的渲染的结果值,每次循环时都计算得到该值,然后供下次循环时使用
	// 新的 BaseState,用于下次渲染的
    let newBaseState = null;
    // 新的 basequeue
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    do {
    
    
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
    
    
        // 优先级不足,跳过此更新,放到暂存执行的队列中
        const clone: Update<S, A> = {
    
    
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
        // 如果第一次出现了不能执行的了,我们要把当前的计算结果保存下来,因为我们会将此节点到最后的所有节点都存储起来,所以此时我们的 newBaseState 就是当前得到的值,这样下次渲染的时候,我们获得的就是这个不能执行的节点前的执行结果
        if (newBaseQueueLast === null) {
    
    
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
    
    
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 更新优先级
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        // 标识跳过的任务的优先级
        markSkippedUpdateLanes(updateLane);
      } else {
    
    
        // 之前的节点有不能执行的,那么后面的节点都要缓存
        if (newBaseQueueLast !== null) {
    
    
          const clone: Update<S, A> = {
    
    
            // 该update需要执行,所以我们永远不能跳过他,使用NoLane优先级,可以避免上面的判断会跳过该步骤
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
		// 已经执行过了(mount中)
        if (update.hasEagerState) {
    
    
          newState = ((update.eagerState: any): S);
        } else {
    
    
          // 计算出当前位置的新的state,注意,这个 newState 是作为这一步的返回结果的,不一定我们的 newBaseState,所以就算要被缓存的节点,只要能执行就需要处理
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    if (newBaseQueueLast === null) {
    
    
      // 所有的update都执行了,那么没有下一次渲染了,所以下一次需要的 baseState就是计算结果
      newBaseState = newState;
    } else {
    
    
      // 有低优先级的update任务,则next指针指向到第1个,形成单向环形链表,
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    // 若newState和之前的state不一样,标记该fiber需要更新
    if (!is(newState, hook.memoizedState)) {
    
    
      markWorkInProgressReceivedUpdate();
    }
    hook.memoizedState = newState; // 整个update链表执行完,得到的newState,用于本次渲染时使用
    hook.baseState = newBaseState; // 下次执行链表时的初始值
    hook.baseQueue = newBaseQueueLast; // 新的update链表,可能为空
    queue.lastRenderedState = newState; // 将本次的state存储为上次rendered后的值
  }
  // 交错更新,省略.....
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

useReducer 的定义和使用

useReduceruseState 类似,但是它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

const [state, dispatch] = useReducer(reducer, initState,init);

useReducer接收两个参数:

  • 第一个参数:reducer函数,它告诉我们根据传入的 state action 怎么样计算返回一个最新的 newState

  • 第二个参数:初始化的hook 传入的 state

  • 第三个参数(可选):用于懒创建state的函数,如果我们使用了第三个参数,那么第二个参数会作为第三个参数的参数传入懒创建函数,第三个参数的返回值是将作为我们的 state

    const initFunc = (initialCount) => {
          
          
        if (initialCount !== 0) {
          
          
            initialCount=+0
        }
      return {
          
          count: initialCount};
    }
    
    const [state, dispatch] = useReducer(reducer, initialCount, initFunc);
    
  • 返回值为最新的 state 和 dispatch 函数,其用来触发 reducer 函数

在我们初始化的时候,我们的 state 也就绑定在了 reducer 函数中,此时,我们只需要在调用返回的 dispatch 时传入我们的 action,就可以触发对应的操作,下面是一个简单的例子,我们使用 useReducer 编写了一个具有加减功能的计数器:

const initialState = {
    
    count: 0};

const reducer = (state, action) => {
    
    
  switch (action.type) {
    
    
    case 'increment':
      return {
    
    count: state.count + 1};
    case 'decrement':
      return {
    
    count: state.count - 1};
    default:
      throw new Error();
  }
};

function Counter() {
    
    
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {
    
    state.count}
      <button onClick={
    
    () => dispatch({
    
    type: 'decrement'})}>-</button>
      <button onClick={
    
    () => dispatch({
    
    type: 'increment'})}>+</button>
    </>
  );
}

同样的,如果 useReducer 返回的值和当前的一样,React不会更新组件,因为React内部使用了Object.is 的语法

useReducer 源码

根据上面的描述我们知道了,useReducer 其实就是一个简单的可以自定义更新方法的 useState ,那么他们的源码也应该极为相似,所以这里我们就直接给出源码和注释:

  • mountReducer 方法和 mountState 的差距仅仅是我们需要通过 第二个第三个参数来初始化我们的 state,最后同样是返回我们的 state 和 dispatch触发器
  • updateReducer 方法已经给出了,只不过我们在 updateReducer 方法中我们传入了 basicStateReducer ,而此处,我们使用了自定义的 reducer 函数,所以此处我们需要一个 (state, action) => newState 的函数作为参数
function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
    
    
  const hook = mountWorkInProgressHook();
  let initialState;
  // 如果是懒创建的 initState,我们调用函数得到其值,否则直接获取其值
  if (init !== undefined) {
    
    
    initialState = init(initialArg);
  } else {
    
    
    initialState = ((initialArg: any): S);
  }
  // 初始化 memoizedState 和 baseState
  hook.memoizedState = hook.baseState = initialState;
  // 初始化更新队列
  const queue: UpdateQueue<S, A> = {
    
    
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  // 创建 dispatch
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

拓展&面试题

解读完了源码,我们来结合源码讲几个面试题:

  1. useState 是异步还是同步

这题相信大家都已经明白了,之前我们在 setState 中也讲过,useState 的更新是跟我们的 Lane 优先级联动了,它每次调用给出的 setState 的时候,并没有立即执行这个更新返回新的值,而是创建一个更新并且保存在 hook 的数据结构中,之后触发更新,只有满足当前 Fiber 优先级的的更新会被执行,所以显然它是异步的,如果你想马上获得更细完毕的 state,我们也给出了解决方案

  1. 一并多个调用 dispatch 会触发多次更新吗

这题我们也应该很快得出答案,如下的多次触发 dispatch ,他们会生成多个更新任务,显然这些任务的优先级是相同的,那么在处理这些更新的时候,也会在一次渲染中一并处理,所以不会触发多次更新

  const handleClick = () => {
    
    
    setCount(count => count + 1);
    setCount(count => count + 1);
  };
  1. 使用 props 作为 useState 的值会动态改变吗

这题也很简单,形如下的赋值,只有在第一次调用初始化的时候使用的 mount 逻辑,此时会把 props.count 的值赋给我们的 hook,此后不论 props.count 如何变化,都只会触发 update 逻辑,那么 props.count 的值就不会产生影响了

const [count, setCount] = useState(props.count);

如果要实现题目要求的效果,我们应该使用 useEffectuseState 配合使用

function App(props) {
    
    
  const [count, setCount] = useState(props.count);

  useEffect(() => {
    
    
    setCount(props.count);
  }, [props.count]);
}

总结

最近在忙 OSPP。也是抽空完成了 useReduceruseState 两个为赋值相关 hooks 的源码解读,他们的逻辑类似:

  • mount 的时候,传入一个初始值(或者函数),然后建立一个更新队列,最后返回一个 dispatch 作为我们的修改 state 的触发器
  • dispatch 在我们调用的时候创建了一个更新,放到了我们的更新队列中,之后开始一次更新的调度
  • 当调度到更新组件时,会 update 逻辑,这个逻辑会根据当前 fiber 的优先级来判定更新队列中哪些更新可以触发,然后返回这次渲染更新后的新 state,对于暂时不能触发的更新,我们将他们按照逻辑缓存起来
  • 触发更新时,useState 使用一个默认的函数来触发更新,可以选择传入一个数值或者使用一个函数接收上一次更新后产生的 state 来触发更新;而 useReducer 则允许我们自定义更新函数的逻辑

那么接下来离预定的 hook 部分还差一个 useEffect,我会尽快完成这个 React hook 最后的讲解,敬请期待!

猜你喜欢

转载自blog.csdn.net/weixin_46463785/article/details/130797287