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。

相关推荐
Aolith2 小时前
从一堆 Bug 到一行代码:我是如何用 keep-alive 优雅解决 Vue 滚动位置恢复的
vue.js·全栈
独泪了无痕2 小时前
利用vue-pdf-embed实现PDF文件的预览
前端·vue.js
xkxnq2 小时前
第七阶段:企业级项目实战核心能力(118天)Vue项目缓存策略:接口缓存(内存+本地)+ 组件缓存+路由缓存组合方案
vue.js·spring·缓存
Highcharts.js2 小时前
无需搭建数据管道,如何快速上线投资基金筛选器?
开发语言·javascript·react.js·前端框架·highcharts
接着奏乐接着舞2 小时前
react native expo打包
javascript·react native·react.js
w_t_y_y4 小时前
VUE组件配置项(零)概述
前端·javascript·vue.js
水云桐程序员4 小时前
Web应用的分类
前端·javascript·vue.js·react.js·webkit
Csvn4 小时前
JS 技巧:设计模式(上)
前端·vue.js