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

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
        }
    }
  }
}

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理