---
# System prepended metadata

title: 'Reack-illustration: hook'
tags: [carryFront]

---

## 状态与副作用
### Fiber 數據結構(中與hook 渲染)相關屬性
- 考虑一个具体的fiber节点如何影响最终的渲染.
- 使用 Hook ：控制Fiber屬性
- 二类属性十分关键in fiber object on renders:
    ```javascript
    // packages>react-reconciler>src>ReactInternalTypes
    export type Fiber = {
      // 1. fiber节点自身状态相关 (state)
      //    作用只局限于fiber树构造阶段, 直接影响子节点的生成
      pendingProps: any,// passed from ReactElement
      memoizedProps: any,// Last render (kept in 內存)
      updateQueue: mixed,// 存储update更新对象的队列, 每一次发起更新, 都需要在该队列上创建一个update对象.
      memoizedState: any, // 上一次生成子节点之后保持在内存中的局部状态.

      // 2. fiber节点副作用(Effect)相关
      flags: Flags, // 标志位, 表明该fiber节点有副作用
      subtreeFlags: Flags, // v17.0.2未启用
      deletions: Array<Fiber> | null, // v17.0.2未启用
      nextEffect: Fiber | null,
      firstEffect: Fiber | null,
      lastEffect: Fiber | null,
    };
    ```
- 状态是一个静态的功能, 它只能为子节点提供数据源,作用于fiber树构造阶段
- 副作用是一个动态功能, 由于它的调用时机是在fiber树渲染阶段, 能获取突变前快照, 突变后的DOM节点等. 甚至通过调用api发起新的一轮fiber树构造, 引发更多的副作用
### 外部 api (外部操作修改到fiber object的方式)
- Class component
    ```javascript
    class App extends React.Component {
      constructor() {
        this.state = {a: 1,};// 初始状态  
      }
      changeState = () => {
        this.setState({ a: ++this.state.a }); // 进入reconciler流程
      };

      // 生命周期函数: 状态相关
      static getDerivedStateFromProps(nextProps, prevState) {...}
      shouldComponentUpdate(newProps, newState, nextContext) {...}

      // 生命周期函数: 副作用相关 fiber.flags |= ...
      componentDidMount() {...} // Update
      getSnapshotBeforeUpdate(prevProps, prevState){...}// Snapshot
      componentDidUpdate() {...} // Update

      // 返回下级ReactElement对象
      render() {
        return <button onClick={this.changeState}>{this.state.a}</button>;
      }
    }
    ```
    >  class组件会实例化一个instance所以拥有独立的局部状态 

- Function component 
    ```javascript
    function App() {
      // 状态相关: 初始状态
      const [a, setA] = useState(1);

      // 副作用相关: fiber.flags
      useEffect(() => {...}, []); // Passive
      useLayoutEffect(() => {...}, []); // Update

      // 返回下级ReactElement对象
      return <button onClick={changeState}>{a}</button>;
    }
    ```
    >  function组件不会实例化, 它只是被直接调用, 无法维护一份独立的局部状态;
       只能依靠Hook对象间接实现局部状态 
       [Built-in React Hooks](https://react.dev/reference/react/hooks)




## Hook 
### 概览
- Use state and other React features without writing a class.
- Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
    - Hooks also offer a new powerful way to combine them.
- Solve problems in React (class components)
    - Reuse stateful logic between components (easier) 
    - Complex components become hard to understand
    - Classes confuse machines
        - hot reloading flaky and unreliable
        - encourage unitential patterns
        - don't minify very well  



    > **Hooks are a new addition in React 16.8. at React Conf 2018**
    > [Introducing Hooks](https://legacy.reactjs.org/docs/hooks-intro.html) from old React official website
    > [Making Sense of React Hooks](https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889) Dan Abramov
    > [Why Do React Hooks Rely on Call Order?](https://overreacted.io/why-do-hooks-rely-on-call-order/) overreacted


### State Hook / Effect Hook
舊版官网上分为了 2 个类别, 状态Hook(State Hook), 和副作用Hook(Effect Hook).
- State Hook
    - 廣義：能实现数据持久化且没有副作用的
        Hook,useState, useReducer -->  useContext, useRef, useCallback, useMemo
    - 得益于`**双缓冲技术(double buffering)**`, 在多次render时, 以fiber为载体, 保证复用同一个Hook对象, 进而实现数据持久化. 
- Effect Hook
    -  修改`fiber.flags`
    -  在performUnitOfWork->completeWork阶段, 所有存在副作用的fiber节点, 都会被添加到父节点的副作用队列后, 最后在commitRoot阶段处理这些副作用节点
    -  副作用回調
- Fiber角度
    ```javascript
    export type Fiber = {
      // 1. fiber节点自身状态相关 (state)
      pendingProps: any,// props
      memoizedProps: any,// Last render (內存)
      updateQueue: mixed,// queue of state updates and callbacks
      memoizedState: any, // state used to create the output

      // 2. fiber节点副作用(Effect)相关
      flags: Flags, // 标志位, 表明该fiber节点有副作用
      subtreeFlags: Flags, // v17.0.2未启用
      deletions: Array<Fiber> | null, // v17.0.2未启用
      nextEffect: Fiber | null,
      firstEffect: Fiber | null,
      lastEffect: Fiber | null,
    };
    ```

### Hook 數據結構
- packages/react-reconciler/ReactFiberHooks.js
    ```javascript
    export type Update<S, A> = {
      lane: Lane,
      revertLane: Lane,
      action: A,
      hasEagerState: boolean,
      eagerState: S | null,
      next: Update<S, A>,
    };

    export type UpdateQueue<S, A> = {
      pending: Update<S, A> | null,
      lanes: Lanes,
      dispatch: (A => mixed) | null,
      lastRenderedReducer: ((S, A) => S) | null,
      lastRenderedState: S | null,
    };

    export type Hook = {
      memoizedState: any, // current 保持在内存中的局部状态
      baseState: any, // baseQueue中所有update对象合并之后的状态
      baseQueue: Update<any, any> | null,  //  存储update对象的环形链表, 只包括高于本次渲染优先级的update对象
      queue: any,// 存储update对象的环形链表, 包括所有优先级的update对象
      next: Hook | null, // next指针, 指向链表中的下一个hook
    };
    ```

- Hook是一个链表, 单个Hook拥有自己的状态hook.memoizedState和自己的更新队列hook.queue


    

### 調用處理函數
hook創建相關位置：react-reconciler 包裡的 [ReactFiberHooks.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js)
調用時間：執行任務回調裡的 beginWork => updateFunctionComponent => renderWithHooks
- state: 樹構造; effect: 樹渲染
![jx-1](https://hackmd.io/_uploads/rkGGxh296.png)

#### 1. ReactFiberHooks.js 檔案構造:調用前於頭部設置全局變量：
source code: [ReactFiberHooks.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L157C1-L194C49)
```javascript!
let renderLanes: Lanes = NoLanes;// 渲染优先级

// 当前正在构造的fiber, 等同于 workInProgress, 为了和当前hook区分, 所以将其改名
let currentlyRenderingFiber: Fiber = (null: any);

// Hooks被存储在fiber.memoizedState 链表上
let currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState

let workInProgressHook: Hook | null = null; // workInProgressHook = fiber(workInProgress).memoizedState

// 在function的执行过程中, 是否再次发起了更新. 只有function被完全执行之后才会重置.
// 当render异常时, 通过该变量可以决定是否清除render过程中的更新.
let didScheduleRenderPhaseUpdate: boolean = false;

// 在本次function的执行过程中, 是否再次发起了更新. 每一次调用function都会被重置
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;

const RE_RENDER_LIMIT = 25;
```

#### 2. renderWithHooks(); 調用hook相關函數 fiber->hook --- **[MOUNT]**
**如果使用了Hook api(如: useEffect, useState), 就会创建一个与之对应的Hook对象**
```javascript!
// ...省略无关代码
export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  // --------------- 1. 设置全局变量 -------------------
  renderLanes = nextRenderLanes; // 当前渲染优先级
  currentlyRenderingFiber = workInProgress; // 当前fiber节点, 也就是function组件对应的fiber节点

  // 清除当前fiber的遗留状态
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // --------------- 2. 调用function,生成子级ReactElement对象 -------------------
  // 指定dispatcher, 区分mount和update
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;
  // 执行function函数, 其中进行分析Hooks的使用
  let children = Component(props, secondArg);

  // --------------- 3. 重置全局变量,并返回 -------------------
  // 执行function之后, 还原被修改的全局变量, 不影响下一次调用
  renderLanes = NoLanes;
  currentlyRenderingFiber = (null: any);

  currentHook = null;
  workInProgressHook = null;
  didScheduleRenderPhaseUpdate = false;

  return children;
}
```
### 创建 Hook
- 初次渲染 mount
    - mountState(); mountEffect()->mountEffectImpl()
    - 內存結構 
        - 前
        ![mount-before-renderwithhooks.8aa55c5e](https://7km.top/static/mount-before-renderwithhooks.8aa55c5e.png)
        - 後: **创建Hook并挂载到fiber.memoizedState上, 多个Hook以链表结构保存**
        ![after-mount](https://7km.top/static/mount-after-renderwithhooks.fb7b72a5.png)
    - hook 初始化 (useState()-> **`mountState`** / useEffect()->**`mountEffectImpl`**)
        - 在mountState內設置：
            - hook.memoizedState
            - hook.baseState
            - hook.queue
        - 回傳： hook.memoizedState, dispatch
        ![initial-hook](https://7km.top/static/initial-state.4c6539c6.png)
        
        > 状态Hook或副作用Hook都按照调用顺序存储在fiber.memoizedState链表中.

- 對比更新 update
    - 点击button, 通过dispatch函数进行更新, dispatch实际就是 [dispatchAction](https://github.com/facebook/react/blob/v18.2.0/packages/react-server/src/ReactFizzHooks.js) :
        
        ```javascript
            function dispatchAction<S, A>(
              fiber: Fiber,
              queue: UpdateQueue<S, A>,
              action: A,
            ) {
              // 1. 创建update对象
              const eventTime = requestEventTime();
              const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
              const update: Update<S, A> = {
                lane,
                action,
                eagerReducer: null,
                eagerState: null,
                next: (null: any),
              };

              // 2. 将update对象添加到hook.queue.pending队列
              const pending = queue.pending;
              if (pending === null) {
                // 首个update, 创建一个环形链表
                update.next = update;
              } else {
                update.next = pending.next;
                pending.next = update;
              }
              queue.pending = update;

              const alternate = fiber.alternate;
              if (
                fiber === currentlyRenderingFiber ||
                (alternate !== null && alternate === currentlyRenderingFiber)
              ) {
                // 渲染时更新, 做好全局标记
                didScheduleRenderPhaseUpdateDuringThisPass =
                  didScheduleRenderPhaseUpdate = true;
              } else {
                // ...省略性能优化部分, 下文介绍

                // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
                scheduleUpdateOnFiber(fiber, lane, eventTime);
              }
            }
        ```
    - mountEffect()->mountEffectImpl()
    - 內存結構 
        - 前
        ![before-update](https://7km.top/static/update-before-renderwithhooks.4cb2417e.png)
        - 後: **创建Hook并挂载到fiber.memoizedState上, 多个Hook以链表结构保存**
        ![after-update](https://7km.top/static/update-after-renderwithhooks.e3518dec.png)






> Referance: 
> [Why Do React Hooks Rely on Call Order?](https://overreacted.io/why-do-hooks-rely-on-call-order/)  by Dan Abramov
> - Hooks are like functional mixins that let you create and compose your own abstractions.
> - Hooks is that they rely on persistent call index between re-renders.
> - 




## useState
### 狀態初始化
在fiber初次构造阶段:
- useState对应源码mountState ; useReducer对应源码mountReducer
- 創建 hook ()
- return [當前状态, dispatch函数]
```javascript!
    function mountState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      // 1. 创建hook
      const hook = mountWorkInProgressHook();
      if (typeof initialState === 'function') {
        initialState = initialState();
      }
      // 2. 初始化hook的属性
      // 2.1 设置 hook.memoizedState/hook.baseState
      // 2.2 设置 hook.queue
      hook.memoizedState = hook.baseState = initialState;
      const queue = (hook.queue = {
        pending: null,
        dispatch: null,
        // queue.lastRenderedReducer是内置函数
        lastRenderedReducer: basicStateReducer,
        lastRenderedState: (initialState: any),
      });
      // 2.3 设置 hook.dispatch
      const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch =
        (dispatchAction.bind(null, currentlyRenderingFiber, queue): any));

      // 3. 返回[当前状态, dispatch函数]
      return [hook.memoizedState, dispatch];
    }
```
![init-hook](https://7km.top/static/initial-state.4c6539c6.png)


### 狀態更新
#### dispatchAction
点击button, 通过dispatch函数进行更新, dispatch实际就是dispatchAction:
- 创建update对象, 其中update.lane代表优先级
- 将update对象添加到hook.queue.pending环形链表
- 发起调度更新: 调用scheduleUpdateOnFiber, 进入reconciler 运作流程中的输入阶段
  （進入react-reconciler包 再次調用 ReactFiberHooks.js內提供的 updateState/updateReducer)
    ```javascript!
    function dispatchAction<S, A>(
      fiber: Fiber,
      queue: UpdateQueue<S, A>,
      action: A,
    ) {
      // 1. 创建update对象
      const eventTime = requestEventTime();
      const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
      const update: Update<S, A> = {
        lane,
        action,
        eagerReducer: null,
        eagerState: null,
        next: (null: any),
      };

      // 2. 将update对象添加到hook.queue.pending队列
      const pending = queue.pending;
      if (pending === null) {
        // 首个update, 创建一个环形链表
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      queue.pending = update;

      const alternate = fiber.alternate;
      if (
        fiber === currentlyRenderingFiber ||
        (alternate !== null && alternate === currentlyRenderingFiber)
      ) {
        // 渲染时更新, 做好全局标记
        didScheduleRenderPhaseUpdateDuringThisPass =
          didScheduleRenderPhaseUpdate = true;
      } else {
        // ...省略性能优化部分, 下文介绍

        // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    ```
#### updateReducer
- 调用updateWorkInProgressHook获取workInProgressHook对象
- 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
- 狀態計算 
    ```javascript
    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: (I) => S,
    ): [S, Dispatch<A>] {
      // 1. 获取workInProgressHook对象
      const hook = updateWorkInProgressHook();

      // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
      const pendingQueue = queue.pending;

      // 3. 状态计算
      if (baseQueue !== null) {
        const first = baseQueue.next;
        let newState = current.baseState;

        let newBaseState = null;
        let newBaseQueueFirst = null;
        let newBaseQueueLast = null;
        let update = first;

        do {
          const updateLane = update.lane;
          // 3.1 优先级提取update
          if (!isSubsetOfLanes(renderLanes, updateLane)) {
            // 优先级不够: 加入到baseQueue中, 等待下一次render
            const clone: Update<S, A> = {
              lane: updateLane,
              action: update.action,
              eagerReducer: update.eagerReducer,
              eagerState: update.eagerState,
              next: (null: any),
            };
            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) {
              // 更新baseQueue
              const clone: Update<S, A> = {
                lane: NoLane,
                action: update.action,
                eagerReducer: update.eagerReducer,
                eagerState: update.eagerState,
                next: (null: any),
              };
              newBaseQueueLast = newBaseQueueLast.next = clone;
            }
            if (update.eagerReducer === reducer) {
              // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer
              newState = ((update.eagerState: any): S);
            } else {
              const action = update.action;
              // 调用reducer获取最新状态
              newState = reducer(newState, action);
            }
          }
          update = update.next;
        } while (update !== null && update !== first);

        // 3.2. 更新属性
        if (newBaseQueueLast === null) {
          newBaseState = newState;
        } else {
          newBaseQueueLast.next = (newBaseQueueFirst: any);
        }
        if (!is(newState, hook.memoizedState)) {
          markWorkInProgressReceivedUpdate();
        }
        // 把计算之后的结果更新到workInProgressHook上
        hook.memoizedState = newState;
        hook.baseState = newBaseState;
        hook.baseQueue = newBaseQueueLast;
        queue.lastRenderedState = newState;
      }

    ```
    

    - 拼接前後 updateReducer() before ＆ after
    ![before-update](https://7km.top/static/before-basequeue-combine.4796299d.png)
    --
    ![after-update](https://7km.top/static/after-basequeue-combine.aeda41e5.png)
    
    - 状态计算 
        優先級不夠；加入baseQueue等待下一次render
        優先級足夠：狀態合併
        [原文](https://7km.top/main/hook-state#%E7%8A%B6%E6%80%81%E6%9B%B4%E6%96%B0)
        ![before-clac](https://7km.top/static/async-update-after-combine.a458fd16.png)
        ![after-clac](https://7km.top/static/async-update-state-compute.c60fa7d8.png)

 
> Referance: 
> [How does useState know what to do](https://overreacted.io/how-does-setstate-know-what-to-do/) by Dan Abramov
> [How do useState work](https://jser.dev/2023-06-19-how-does-usestate-work/)

