• class component 代码重,且复用主要依赖 hoc、mixin,粒度大,代码组织比较混乱等;引入 fc,能让你更简洁定义组件代码,引入 hook,能在 fc 中使用 state 及其它 react 特性.
  • 原理简述:hook 底层会将状态挂载到 fiber 节点,在下次更新时就可以拿到上次状态,从而实现有副作用效果的 hook 功能
  • 根据 current 节点状态判断是否第一次运行,是的话走 mountState 函数,不是第一次运行,走 updateState

    1.mountState:核心逻辑:

    • 调用 mountWorkInProgressHook 函数,创建当前 hook 对象:

      1. - 如果是第一个 hookfirstWorkInProgressHook = workInProgressHook = hook,也就是挂载到 fiber 节点的 memoizedState 链表,作为链表的第一个节点!!!
      2. - 如果是后面的hook,则 `workInProgressHook = workInProgressHook.next = hook;`,也就是挂载到 fiber 节点 memoizedState 链表的最后一个节点
      3. - fiber.memoizedState 形成链表结构,按 hook 顺序存储每一个 hook 的状态,这很重要!!!

      ```javascript // 在每个hook挂载时的执行函数。 保存在全局变量中。 形成链表 function mountWorkInProgressHook() { var hook = { memoizedState: null,

      baseState: null, queue: null, baseUpdate: null,

      next: null }; if (workInProgressHook === null) { // 挂载到最后一个 firstWorkInProgressHook = workInProgressHook = hook; } else { // 增加一个 workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; }

// 最终的hook链表, 保存在函数组件对应fiberNode上 fiberNode.memoizedState = firstWorkInProgressHook = { // 组件内最终使用的值 memoizedState: null,

  1. baseState: null,
  2. queue:null | {
  3. // 链接action形成的update对象
  4. last: null,
  5. dispatch: null,
  6. eagerReducer: basicStateReducer,
  7. eagerState: initialState
  8. },
  9. baseUpdate: null,
  10. // 指向下一个hook节点
  11. next: null

} // update对象,会挂载在首次mount时绑定的queue对象的last指针上 const update = { expirationTime: _expirationTime, action: action, eagerReducer: null, eagerState: null, // 链接下一个update对象的指针,会形成一个环状链表 next: null }

  1. - `dispatchSetState`setState 函数
  2. - 执行 action,计算新 state 值,并将这些更新信息保存到 update 对象中
  3. - 调用 `enqueueUpdate` 函数将更新任务添加到 hook 更新队列最后
  4. - 调用 `scheduleUpdateOnFiber` 调度更新任务,最终调用 `updateState`函数
  5. <a name="OGgua"></a>
  6. ### 2.`updateState`:
  7. 底层调用 `updateWorkInProgressHook`,这里关键逻辑就是按照调用顺序,在 `memoiedState`中找到对应的 hook 记录
  8. ```javascript
  9. export function xx(){
  10. const [data, setData] = useState(null);
  11. const [age, setAge] = useState(10);
  12. }
  13. fiber.memoizedState === hook[data]
  14. hook[data].next === hook[age]
  1. - 之后,`updateReducer` 中循环执行所有 update 任务,计算出最新 state
  2. - state 值保存到 hook.memoridState 变量,完成更新
  3. - react 会用最新的 state 值计算 vdom,实现状态更新
  • 为什么不能在循环、if 等语句中调用 hook?为了确保每次 render 都是按相同的顺序执行 hook

    • 因为会导致 memoiedState 链表结构发生变化,mount 与 update 就对不上了,很容易出问题
  • 与 Vue 相比:

    • 共同点:都用于实现更细粒度逻辑复用;都能够实现有副作用的状态管理;
    • 区别:
      1. - react 任意 state 变更 —— 无论实际有没有参与组件渲染,都会导致组件重新 rendervue 则有响应式加持,仅当 render 函数依赖的属性发生变更时才重新渲染
      2. - react 会重复执行,hook 也会重复执行,需要用 useCallback 等缓存函数;vue setup 只会执行一次,hook 也只会有一次,gc 压力较小,也不需要考虑函数缓存
      3. - react 的副作用通过把状态挂载到 fiber 节点上;vue 则是给数据加上响应式能力
      4. - vue 对调用顺序没什么要求;react 有严格要求
      5. - react hook 依赖需要手动声明;vue 不需要
  • 最佳实践:
    • state 初始化函数 useState(initFunc) 只会执行一次,所以 state 值不应该依赖 props 等可变参数
    • 绝对不要在 for 、if 语句中执行hook;尽量保持在 render 函数顶层作用域执行hook

useState返回的为啥是数组而不是对象