概述

React 在 v16.8 提供了 Hook 特性,React Hooks 通过增强函数式组件,为 Function Component 注入一些功能,例如 useState 让原本的 Stateless Function Component 有了状态。

工作原理

接下来我们从 useState 这个 hook 为切入点,打开 React Hooks 源码看看背后的原理。
先看个 Demo:

  1. import React, { useState } from 'react';
  2. function App() {
  3. const [count, dispatchCount] = useState(0);
  4. return (
  5. <div>
  6. <span>{count}</span>
  7. <button onClick={() => dispatchCount(count + 1)}>
  8. increment
  9. </button>
  10. </div>
  11. )
  12. }

上述 Demo 使用函数组件定义了一个计数器,相对于普通函数组件,该组件提供了 count 的状态,每点击按钮一次 count 就加 1。
接下来我们看下 useState 的源码,它是如何保存 count 状态、更新 count 状态的。

状态保存与更新

去除无关代码之后,可以看到我们调用的 useState 只是一个入口,最终是调用 dispatcher 的一个方法。并且在 React.js 中只负责定义。

  1. // react/src/ReactHooks.js
  2. const ReactCurrentDispatcher = {
  3. /**
  4. * @internal
  5. * @type {ReactComponent}
  6. */
  7. current: (null: null | Dispatcher),
  8. };
  9. export function useState<S>(
  10. initialState: (() => S) | S,
  11. ): [S, Dispatch<BasicStateAction<S>>] {
  12. const dispatcher = resolveDispatcher();
  13. return dispatcher.useState(initialState);
  14. }
  15. function resolveDispatcher() {
  16. const dispatcher = ReactCurrentDispatcher.current;
  17. return dispatcher;
  18. }

Hook 只有在 FunctionalComponent 更新的时候才会被调用,在 updateFunctionComponent 的方法中找到了 Hook 更新的入口 renderWithHooks,在 renderWithHooks 中依照条件对 ReactCurrentDispatcher.current 进行了赋值。

  1. // react-reconciler/src/ReactFiberHooks.js
  2. function renderWithHooks(
  3. current: Fiber | null,
  4. workInProgress: Fiber,
  5. Component: any,
  6. props: any,
  7. secondArg: any,
  8. nextRenderExpirationTime: ExpirationTime,
  9. ) {
  10. // ... 省略无关代码
  11. ReactCurrentDispatcher.current =
  12. current === null || current.memoizedState === null
  13. // Mount
  14. ? HooksDispatcherOnMount
  15. // Update
  16. : HooksDispatcherOnUpdate;
  17. // ...
  18. }

可以看到 Dispatcher 分为 Mount 和 Update。这里我们可以找到对应的 mountStateupdateState,其他 hook 也是这么分类的,如果我们要查看其他 hook 代码,均可以在这里找到对应时机的代码。

状态保存

在了解具体代码之前,我们先了解下 Hook 的定义.

  1. Hook = {
  2. // 当前 hook 的 state,就是上述 Demo 中的 count 值
  3. memoizedState: any,
  4. // 多次调用,保存队列
  5. queue: UpdateQueue<any, any> | null,
  6. // 下一个 hook,通过该属性连接成一个 hook 的单向链表
  7. next: Hook | null,
  8. |};

对于一个组件内的 hook 对象,会被保存在 App 组件对应的 Fiber 对象的 memoizedState 中。保存结构大致如下:

  1. fiber = {
  2. memoizedState: {
  3. memoizedState: initialState,
  4. queue: {},
  5. next: {
  6. memoizedState: initialState,
  7. queue: {},
  8. next: null,
  9. },
  10. }
  11. }

状态更新

当首次渲染时,调用 mountState 时,返回 [hook.memoizedState, dispatch]

  1. // react-reconciler/src/ReactFiberHooks.js
  2. // 获取 hook 对象
  3. function mountWorkInProgressHook(): Hook {
  4. const hook: Hook = {
  5. memoizedState: null,
  6. baseState: null,
  7. baseQueue: null,
  8. queue: null,
  9. next: null,
  10. };
  11. if (workInProgressHook === null) {
  12. // 将 hook 加到 fiber.memoizedState
  13. currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  14. } else {
  15. // 插入链表,指定下一个 hook
  16. workInProgressHook = workInProgressHook.next = hook;
  17. }
  18. return workInProgressHook;
  19. }
  20. function mountState<S>(
  21. initialState: (() => S) | S,
  22. ): [S, Dispatch<BasicStateAction<S>>] {
  23. const hook = mountWorkInProgressHook();
  24. if (typeof initialState === 'function') {
  25. initialState = initialState();
  26. }
  27. hook.memoizedState = hook.baseState = initialState;
  28. const queue = (hook.queue = {
  29. pending: null,
  30. dispatch: null,
  31. lastRenderedReducer: basicStateReducer,
  32. lastRenderedState: (initialState: any),
  33. });
  34. const dispatch: Dispatch<
  35. BasicStateAction<S>,
  36. > = (queue.dispatch = (dispatchAction.bind(
  37. null,
  38. currentlyRenderingFiber,
  39. queue,
  40. ): any));
  41. return [hook.memoizedState, dispatch];
  42. }

调用上述 Demo 中 dispatchCount,其实就是调用 dispatchAction.bind(null, currentlyRenderingFiber, queue),

  1. function dispatchAction<S, A>(
  2. fiber: Fiber,
  3. queue: UpdateQueue<S, A>,
  4. action: A,
  5. ) {
  6. // 每调用一次 dispatchCount,都会创建一个 update 对象,记录要更新的值 action
  7. const update: Update<S, A> = {
  8. action,
  9. next: null,
  10. // ...
  11. };
  12. // ...
  13. // 将更新附加到列表的末尾
  14. const pending = queue.pending;
  15. if (pending === null) {
  16. // 这是第一次更新,创建一个循环列表。
  17. update.next = update;
  18. } else {
  19. // 插入新的 update 节点
  20. update.next = pending.next;
  21. pending.next = update;
  22. }
  23. queue.pending = update;
  24. // ...
  25. // 更新渲染调度
  26. scheduleWork()
  27. }

更新 state

当调用 dispatchCount 时,这时候实际是调用 updateState 对 state 进行合并处理,在 updateReducer 中会遍历 hook 链表,得到最新 memoizedState 并返回。

  1. // react-reconciler/src/ReactFiberHooks.js
  2. function updateState<S>(
  3. initialState: (() => S) | S,
  4. ): [S, Dispatch<BasicStateAction<S>>] {
  5. return updateReducer(basicStateReducer, (initialState: any));
  6. }
  7. function updateReducer<S, I, A>(
  8. reducer: (S, A) => S,
  9. initialArg: I,
  10. init?: I => S,
  11. ) {
  12. const hook = updateWorkInProgressHook();
  13. const queue = hook.queue;
  14. // ...
  15. let first = baseQueue.next;
  16. do {
  17. // 获取传入的 state action
  18. const action = update.action;
  19. // 更新 state
  20. newState = reducer(newState, action);
  21. // 遍历下一个更新 action
  22. update = update.next;
  23. } while (update !== null && update !== first)
  24. hook.memoizedState = newState;
  25. const dispatch: Dispatch<A> = (queue.dispatch: any);
  26. return [hook.memoizedState, dispatch];
  27. }

小结

对于 useState 的逻辑,就相当于原有的 class 组件的 state,只是在函数组件中,他的状态存在 Fiber 节点上。通过链表操作遍历更新该 Fiber 节点下的 hook 对象来更新函数组件中的 state。