关于前端:react-hooks源码深入浅出一

38次阅读

共计 2938 个字符,预计需要花费 8 分钟才能阅读完成。

react hooks 在 react 16.8 版本推出后就广受好评,因为它很好的解决了旧版本 react 无奈很好的在状态逻辑上复用的问题,同时官网阐明 hooks 会向后兼容不存在 breaking changes,在我的项目中更好的无缝接入。

背景和意义

  1. 目前我的项目中 hooks 应用越来越遍及,咱们作为开发者不仅要知其然还要知其所以然
  2. 让咱们在应用过程中能更快的定位排查问题、性能调优
  3. 学习和理解优良框架的实现思路

从两个纬度剖析

  1. 首次渲染
  2. 更新阶段

DEMO

咱们以最根本的 demo 开始,其中波及两个根本的 hook:
useState 和 useEffect

一、首次渲染

外围流程


上图就是咱们某一个 hook 组件在首次渲染时所经验的外围流程,大抵分为三步:

  1. 组件类型解析(是 Function、class 类型),而后去执行对应类型的解决办法

    1. 以后 fiberNode(也就是以后组件,react16 引入了 fiber 概念)上进行 hook 的创立和挂载,将咱们所有的 hook api 挂载到全局变量上(dispatcher)
    2. 程序执行以后组件,每遇到一个 hook api 通过 next 把它连贯到咱们的以后组件的 hook 链表上

fiberNode 构造

首次渲染实现后的以后 fiberNode(组件)中的构造关系能够用下图示意:

源码一览

hook api 挂载

首次渲染 currentDispatcher 为空,先挂载所有 hook 到以后 fiberNode 的 dispatcher,其实也就是 HooksDispatcherOnMountInDEV 变量

  {if (currentDispatcher !== null) {currentDispatcher = HooksDispatcherOnUpdateInDEV;} else {currentDispatcher = HooksDispatcherOnMountInDEV;}
  }

看看 HooksDispatcherOnMountInDEV 外部
发现正式咱们相熟的 useState 等各种原生 hook,他们外部其实是调用的 mountXXX 办法

HooksDispatcherOnMountInDEV = {useCallback: function (callback, deps) {return mountCallback(callback, deps);
    },
    useEffect: function (create, deps) {return mountEffect(create, deps);
    },
    useMemo: function (create, deps) {return mountMemo(create, deps);
    },
    useState: function (initialState) {return mountState(initialState);
    }
  }

回到咱们的 demo,首先是 mountState
其实做了三件事:

  1. 创立以后 hook 的链表节点,节点的数据结构为上图红框。<font color=’red’>memorizedState</font> 是咱们最终返回的初始值;<font color=’red’>queue</font> 其实是更新队列,当咱们屡次更新某一状态时须要用 queue 队列存取和遍历;<font color=’red’>next</font> 用来连贯下一个 hook
  2. 将以后 hook 连贯到以后的 fiberNode 的 hook 链表上
  3. 绑定状态更新办法(dispatchAction),并返回[state,dispatchAction]

持续看 demo,到 useEffect,外部实际上执行 mountEffectImpl 办法

function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
    // 创立并获取以后 hook 节点信息
    var hook = mountWorkInProgressHook();

    hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
  }
function mountWorkInProgressHook() {
    // 将以后 hook 连贯到咱们的 hook 链表中
    var hook = {
      memoizedState: null,
      queue: null,
      next: null
    };

    if (workInProgressHook === null) {currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;} else {workInProgressHook = workInProgressHook.next = hook;}

    return workInProgressHook;
  }
function pushEffect(tag, create, destroy, deps) {
    var effect = {
      tag: tag,  // 更新标识
      create: create, // 传入的回调,也就是咱们开发时的第一个参数
      destroy: destroy, // return 的函数,组件销毁时执行的函数
      deps: deps, // 依赖项数组
      next: null
    };
    var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
    
    // 这里做的就是把每个 useEffect hook 独自以链式构造存到了 componentUpdateQueue 这个全局变量中
    if (componentUpdateQueue === null) {componentUpdateQueue = createFunctionComponentUpdateQueue();
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      var lastEffect = componentUpdateQueue.lastEffect;

      var firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
    
    return effect;
  }

综上 useEffect 外部做了两件事:

  1. mountWorkInProgressHook 办法,就是将以后 hook 连贯到咱们的 fiberNode 的 hook 链表中
  2. 定义 effect 对象存储咱们传入的信息,同时将 hook 存入到 <font color=’red’>componentUpdateQueue</font> 更新队列(这个队列是用来专门存储 useEffect hook 的)

至此咱们首次渲染完结,咱们此时 fiberNode 的 hook 链式构造为

// 以后 fiber 节点的外部 hook 链

currentFiber:{
  ...
  memoizedState:{
    memoizedState:xxx,
    ...
    next:{
        memoizedState:xxx,
        ...
        next:{
            memoizedState:xxx,
            ...
            next:hook4
        }
    }
  }
}

更直观一些看的话如下图所示

正文完
 0