react hooks原理:为什么不能在条件中使用 hook ?

著有《React18 设计原理》《javascript地月星》等多个专栏。 欢迎关注。

创作不易,内容有帮助记得 ❤️点赞,⭐️收藏 ,🔥评论 ~

本文全部都是原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解 ~
推荐指数(值得一读):⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

github 👉github.com/chd666233/b...

主页 👉juejin.cn/post/757791...

例子 👉juejin.cn/post/763612...

初始化 :执行了组件函数的时候,每个 useState 都会创建一个 hook、queue。
事件 :setXXX 更新值到对应的 update -> queue -> hook。
触发协调 :再次执行组件函数创建组件、新 hook。旧 hook 的值同步到新 hook。

1. 初始化创建组件、创建组件 hook 和 queue

在组件创建的时候,每个 useXXX 都会调用 mountState 函数。每个 useXXX 都是一个 hook,创建 hook。

js 复制代码
在组件创建的时候,每隔一个useXXX都会调用这个函数
function mountState(initialState) {
  //⭐️
  var hook = mountWorkInProgressHook();

  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }

  hook.memoizedState = hook.baseState = initialState;
  var queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState
  };
  hook.queue = queue;
  var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
  return [hook.memoizedState, dispatch];
}
js 复制代码
function mountWorkInProgressHook() {
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }

  return workInProgressHook;
}

2. 事件中触发 setXXX

js 复制代码
function dispatchSetState(fiber, queue, action) {
  ...
  var lane = requestUpdateLane(fiber);
  var update = {
    lane: lane,
    action: action,
    hasEagerState: false,
    eagerState: null,
    next: null
  };

  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    var alternate = fiber.alternate;

    ...
    var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);

    if (root !== null) {
      var eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  markUpdateInDevTools(fiber, lane);
}

创建一个循环队列。update是一个队列,通过 update.next 指向下一个节点。update 是每一次 state 改变的值,例如输入事件每次的改变:j、jk、jkl...。

没有一直积累 update.next,新的协调会清除 queue.interleaved = null; ,再一次调度会发生 interleaved === null,于是重新积累队列。也就是说,协调后就丢弃了早期的 state 值。这样做节省内存。

js 复制代码
// 创建、维护一个循环队列。update是一个队列,通过update.next指向下一个节点。
function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
  //上次的update。就是末尾节点。
  var interleaved = queue.interleaved;

  if (interleaved === null) {
    update.next = update; 
    pushConcurrentUpdateQueue(queue);
  } else {
    // 新旧尾节点的交接,设置新尾节点的头节点,然后把旧尾节点的末尾设置为新尾节点
    // update是一个队列,通过update.next指向下一个节点。

    //interleaved.next:上次update的next就是头节点。
    update.next = interleaved.next;
    //上次尾节点的next更新为这次update
    interleaved.next = update;
  }
  //queue.interleaved保存尾节点。
  queue.interleaved = update;
  return markUpdateLaneFromFiberToRoot(fiber, lane);
}

//把queue收集到concurrentQueues数组
var concurrentQueues = null;
function pushConcurrentUpdateQueue(queue) {
  if (concurrentQueues === null) {
    concurrentQueues = [queue];
  } else {
    concurrentQueues.push(queue);
  }
}

3. 协调 循环 Fiber 树前

js 复制代码
//这是在遍历 Fiber 树前前调用的。
function finishQueueingConcurrentUpdates() {

  if (concurrentQueues !== null) {
    for (var i = 0; i < concurrentQueues.length; i++) {
      // 取queue
      var queue = concurrentQueues[i];
      //把update 从 queue.interleaved 迁移到 queue.pending
      
      var lastInterleavedUpdate = queue.interleaved;
      if (lastInterleavedUpdate !== null) {
        // 把queue.interleaved清除
        queue.interleaved = null;
        var firstInterleavedUpdate = lastInterleavedUpdate.next;
        var lastPendingUpdate = queue.pending;

        if (lastPendingUpdate !== null) {
          var firstPendingUpdate = lastPendingUpdate.next;
          lastPendingUpdate.next = firstInterleavedUpdate;
          lastInterleavedUpdate.next = firstPendingUpdate;
        }
       // 迁移
        queue.pending = lastInterleavedUpdate;
      }
    }

    concurrentQueues = null;
  }
}

4. 协调 循环 Fiber 树,遍历到一个组件节点,重新创建组件的时候

在组件再次创建的时候,每个 useXXX 都会调用 updateState(不再是 mountState函数)。

重新创建了 hook 链表,需要把 state 变更后的最新值(不再是初始值),同步到新 hook,通过 return hook.memoizedState, dispatch 返回给组件。

4.1. hook 规则 为什么不能在条件语句中使用 hook

新 hook 一开始没有最新的值,要从旧 hook 同步过来。这就是为什么不能在条件语句中使用 hook --------- 新旧链表不一致导致错误!

js 复制代码
创建组件,按照组件开头hook的顺序调用updateState,每个updateState都会取一个hook,下一个updateState取hooks.next
function updateState(initialState) {
  return updateReducer(basicStateReducer);
}

function updateReducer(reducer, initialArg, init) {
  //⭐️updateWorkInProgressHook 将取到旧 hook 链表的头节点,current.memoizedState。
  // updateWorkInProgressHook这里取hook
  var hook = updateWorkInProgressHook();
  var queue = hook.queue;

  ...

  var dispatch = queue.dispatch;
  //返回给const [xxx,setXXX] = useXXX();
  return [hook.memoizedState, dispatch];
}
js 复制代码
function updateWorkInProgressHook() {
  var nextCurrentHook;
  //旧hook链表
  if (currentHook === null) {
    var current = currentlyRenderingFiber$1.alternate;

    if (current !== null) {
      //hook头
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    //下一个hook
    nextCurrentHook = currentHook.next;
  }
  
  var nextWorkInProgressHook;
 //新hook链表
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }
  //新hooks链表已经有这个hook了
  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.
    // 克隆旧hooks到新hooks
    if (nextCurrentHook === null) {
      throw new Error('Rendered more hooks than during the previous render.');
    }

    currentHook = nextCurrentHook;
    var newHook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      //保存到新Fiber memoizedState
      currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }

  return workInProgressHook;
}

结语

新建组件重新创建了 hooks 链表,为了把当前状态同步到新 hook 不得不"查找"对应的旧 hook。

相关推荐
爱因斯坦乐15 小时前
Vue项目整合
前端·javascript·vue.js
ct97816 小时前
组件间的通信
前端·javascript·vue.js
左手吻左脸。17 小时前
Vue 全栈面试题大全(2026 最新版最详细)
前端·javascript·vue.js
Aphasia31117 小时前
手写KeepAlive组件
前端·react.js·面试
小新11018 小时前
最简单但完整的 Vue 响应式示例(一个简单的计数器按钮)
前端·javascript·vue.js
刘海不能乱1619 小时前
Java JUC源码分析系列笔记-Synchronized
vue.js
whatever who cares20 小时前
Vue3中vue文件和composables的分工
前端·javascript·vue.js
薛先生_09921 小时前
vue-编程式跳转-基本跳转
前端·javascript·vue.js
whatever who cares21 小时前
大型 React 项目的文件结构
前端·react.js·前端框架