react hooks源码深入浅出(一)

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

背景和意义

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

从两个阶段出发分析

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

DEMO

我们以最基本的demo开始,其中涉及两个基本的hook:
useState和useEffectreact hooks源码深入浅出(一)_第1张图片

一、初次渲染

核心流程

react hooks源码深入浅出(一)_第2张图片
上图就是我们某一个hook组件在初次渲染时所经历的核心流程,大致分为三步:

  1. 组件类型解析(是Function、class类型),然后去执行对应类型的处理方法

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

fiberNode结构

初次渲染完成后的当前fiberNode(组件)中的结构关系可以用下图表示:
react hooks源码深入浅出(一)_第3张图片

源码一览

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
react hooks源码深入浅出(一)_第4张图片其实做了三件事:

  1. 创建当前hook的链表节点,节点的数据结构为上图红框。memorizedState是我们最终返回的初始值;queue其实是更新队列,当我们多次更新某一状态时需要用queue队列存取和遍历;next用来连接下一个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存入到componentUpdateQueue更新队列(这个队列是用来专门存储useEffect hook的)

至此我们初次渲染结束,我们此时fiberNode的hook链式结构为

// 当前fiber节点的内部hook链

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

更直观一些看的话如下图所示
在这里插入图片描述

你可能感兴趣的:(前端,hooks,react.js,源码分析)