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

背景和意义

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

从两个阶段出发分析

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

DEMO

我们以最基本的demo开始,其中涉及两个基本的hook:

  • useState
  • useEffect
import React ,{useState,useEffect} from 'react';

const Example = props => {
    const [count,setCount] = useState(0);

    useEffect(()=> {
        console.log('xiaoliu test');
    },[count])

    return (
        <div>
            <p>You click {count} me</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    )
}   

export default Example;

一、初次渲染

核心流程

在这里插入图片描述
上图就是我们某一个hook组件在初次渲染时所经历的核心流程,大致分为三步:

  1. 组件类型解析(是Function、class类型),然后去执行对应类型的处理方法
  2. 判断当前为Function类型组件,所以执行相应方法。首先在当前组件(也就是当前fiberNode,react16引入了fiber概念,每一个组件对于react来说其实就是一个fiber节点,所有的fiber节点构成了一颗fiber树,具体概念参考:https://segmentfault.com/a/11...)上进行hook的创建和挂载,将我们所有的hook api挂载到全局变量上(全局变量为dispatcher)
  3. 然后顺序执行当前组件,每遇到一个hook api通过next把它连接到我们的当前fiberNode的hook链表上

fiberNode结构

初次渲染完成后的当前fiberNode(组件)中的结构关系可以用下图表示:
在这里插入图片描述

源码一览

hook api挂载

初次渲染currentDispatcher为空,先挂载所有hook到当前fiberNode的dispatcher,其实也就是将HooksDispatcherOnMountInDEV变量赋值到dispatcher上

  {
    //  首次执行currentDispatcher = null,所以进入else分支;在更新阶段会进入if分支
    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节点,节点的数据结构为上图红框。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的,因为useEffect的回调是异步执行的,所以在一次调和结束页面渲染后会去遍历componentUpdateQueue更新队列遍历执行我们存入的effect回调)

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

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

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

更直观一些看的话如下图所示,其实就是一个单向链表
在这里插入图片描述

初次渲染阶段我们根据两个基础hook深入源码来分析了一下它内部的处理机制,那么在状态更新阶段它内部又是如何实现的?(未完待续...)


了不起的小六先生
48 声望4 粉丝

明明可以靠颜值却要靠技术活命的一只小小程序猿