9.hooks源码(想知道Function Component是怎样保存状态的嘛)

人人都能读懂的react源码解析(大厂高薪必备)

9.hooks源码(想知道Function Component是怎样保存状态的嘛)

视频课程&调试demos

视频课程的目的是为了快速掌握react源码运行的过程和react中的scheduler、reconciler、renderer、fiber等,并且详细debug源码和分析,过程更清晰。

视频课程:进入课程

demos:demo

课程结构:

  1. 开篇(听说你还在艰难的啃react源码)
  2. react心智模型(来来来,让大脑有react思维吧)
  3. Fiber(我是在内存中的dom)
  4. 从legacy或concurrent开始(从入口开始,然后让我们奔向未来)
  5. state更新流程(setState里到底发生了什么)
  6. render阶段(厉害了,我有创建Fiber的技能)
  7. commit阶段(听说renderer帮我们打好标记了,映射真实节点吧)
  8. diff算法(妈妈再也不担心我的diff面试了)
  9. hooks源码(想知道Function Component是怎样保存状态的嘛)
  10. scheduler&lane模型(来看看任务是暂停、继续和插队的)
  11. concurrent mode(并发模式是什么样的)
  12. 手写迷你react(短小精悍就是我)

hook调用入口

在hook源码中hook存在于Dispatcher中,Dispatcher就是一个对象,不同hook 调用的函数不一样,全局变量ReactCurrentDispatcher.current会根据是mount还是update赋值为HooksDispatcherOnMount或HooksDispatcherOnUpdate
ReactCurrentDispatcher.current = 
  current === null || current.memoizedState === null//mount or update
  ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;  
const HooksDispatcherOnMount: Dispatcher = {//mount时
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  //...
};

const HooksDispatcherOnUpdate: Dispatcher = {//update时
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  //...
};

hook数据结构

在FunctionComponent中,多个hook会形成hook链表,保存在Fiber的memoizedState的上,而需要更新的Update保存在hook.queue.pending中
const hook: Hook = {
  memoizedState: null,//对于不同hook,有不同的值
  baseState: null,//初始state
  baseQueue: null,//初始queue队列
  queue: null,//需要更新的update
  next: null,//下一个hook
};

下面来看下memoizedState对应的值

  • useState:例如const [state, updateState] = useState(initialState)memoizedState等于state的值
  • useReducer:例如const [state, dispatch] = useReducer(reducer, {});memoizedState等于state的值
  • useEffect:在mountEffect时会调用pushEffect创建effect链表,memoizedState就等于effect链表,effect链表也会挂载到fiber.updateQueue上,每个effect上存在useEffect的第一个参数回调和第二个参数依赖数组,例如,useEffect(callback, [dep]),effect就是{create:callback, dep:dep,...}
  • useRef:例如useRef(0),memoizedState就等于{current: 0}
  • useMemo:例如useMemo(callback, [dep])memoizedState等于[callback(), dep]
  • useCallback:例如useCallback(callback, [dep])memoizedState等于[callback, dep]useCallback保存callback函数,useMemo保存callback的执行结果

useState&useReducer

之所以把useState和useReducer放在一起,是因为在源码中useState就是有默认reducer参数的useReducer。

  • useState&useReducer声明

    resolveDispatcher函数会获取当前的Dispatcher

    function useState(initialState) {
      var dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }
    function useReducer(reducer, initialArg, init) {
      var dispatcher = resolveDispatcher();
      return dispatcher.useReducer(reducer, initialArg, init);
    }
    
    
  • mount阶段

    mount阶段useState调用mountState,useReducer调用mountReducer,唯一区别就是它们创建的queue中lastRenderedReducer不一样,mount有初始值basicStateReducer,所以说useState就是有默认reducer参数的useReducer。

    function mountState<S>(//
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      const hook = mountWorkInProgressHook();//创建当前hook
      if (typeof initialState === 'function') {
        initialState = initialState();
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState赋值
      const queue = (hook.queue = {//赋值hook.queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: basicStateReducer,//和mountReducer的区别
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<//创建dispatch函数
        BasicStateAction<S>,
      > = (queue.dispatch = (dispatchAction.bind(
        null,
        currentlyRenderingFiber,
        quewque,
      ): any));
      return [hook.memoizedState, dispatch];//返回memoizedState和dispatch
    }
    
    function mountReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = mountWorkInProgressHook();//创建当前hook
      let initialState;
      if (init !== undefined) {
        initialState = init(initialArg);
      } else {
        initialState = ((initialArg: any): S);
      }
      hook.memoizedState = hook.baseState = initialState;//hook.memoizedState赋值
      const queue = (hook.queue = {//创建queue
        pending: null,
        dispatch: null,
        lastRenderedReducer: reducer,
        lastRenderedState: (initialState: any),
      });
      const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(//创建dispatch函数
        null,
        currentlyRenderingFiber,
        queue,
      ): any));
      return [hook.memoizedState, dispatch];//返回memoizedState和dispatch
    }
    
    
    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      return typeof action === 'function' ? action(state) : action;
    }
    
  • update阶段

    update时会根据hook中的update计算新的state

    function updateReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      const hook = updateWorkInProgressHook();//获取hook
      const queue = hook.queue;
      queue.lastRenderedReducer = reducer;
    
      //...更新state和第5章的state计算逻辑基本一致
    
      const dispatch: Dispatch<A> = (queue.dispatch: any);
      return [hook.memoizedState, dispatch];
    }
    
    
  • 执行阶段

    useState执行setState后会调用dispatchAction,dispatchAction做的事情就是讲Update加入queue.pending中,然后开始调度

    function dispatchAction(fiber, queue, action) {
    
      var update = {//创建update
        eventTime: eventTime,
        lane: lane,
        suspenseConfig: suspenseConfig,
        action: action,
        eagerReducer: null,
        eagerState: null,
        next: null
      }; 
    
      //queue.pending中加入update
      
      var alternate = fiber.alternate;
    
      if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
        //如果是render阶段执行的更新didScheduleRenderPhaseUpdate=true
    }
        didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
      } else {
        if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
          //如果fiber不存在优先级并且当前alternate不存在或者没有优先级,那就不需要更新了
          //优化的步骤
        }
    
        scheduleUpdateOnFiber(fiber, lane, eventTime);
      }
    }
    
    

useEffect

  • 声明

    获取并返回useEffect函数

    export function useEffect(
      create: () => (() => void) | void,
      deps: Array<mixed> | void | null,
    ): void {
      const dispatcher = resolveDispatcher();
      return dispatcher.useEffect(create, deps);
    }
    
  • mount阶段

    调用mountEffect,mountEffect调用mountEffectImpl,hook.memoizedState赋值为effect链表

    function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = mountWorkInProgressHook();//获取hook
      const nextDeps = deps === undefined ? null : deps;//依赖
      currentlyRenderingFiber.flags |= fiberFlags;//增加flag
      hook.memoizedState = pushEffect(//memoizedState=effects环状链表
        HookHasEffect | hookFlags,
        create,
        undefined,
        nextDeps,
      );
    }
    
  • update阶段

    浅比较依赖,如果依赖性变了pushEffect第一个参数传HookHasEffect | hookFlags,HookHasEffect表示useEffect依赖项改变了,需要在commit阶段重新执行

    function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
      const hook = updateWorkInProgressHook();
      const nextDeps = deps === undefined ? null : deps;
      let destroy = undefined;
    
      if (currentHook !== null) {
        const prevEffect = currentHook.memoizedState;
        destroy = prevEffect.destroy;//
        if (nextDeps !== null) {
          const prevDeps = prevEffect.deps;
          if (areHookInputsEqual(nextDeps, prevDeps)) {//比较deps
            //即使依赖相等也要将effect加入链表,以保证顺序一致
            pushEffect(hookFlags, create, destroy, nextDeps);
            return;
          }
        }
      }
    
      currentlyRenderingFiber.flags |= fiberFlags;
    
      hook.memoizedState = pushEffect(
        //参数传HookHasEffect | hookFlags,包含hookFlags的useEffect会在commit阶段执行这个effect
        HookHasEffect | hookFlags,
        create,
        destroy,
        nextDeps,
      );
    }
    
  • 执行阶段

    在第9章commit阶段的commitLayoutEffects函数中会调用schedulePassiveEffects,将useEffect的销毁和回调函数push到pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中,然后在mutation之后调用flushPassiveEffects依次执行上次render的销毁函数回调和本次render 的回调函数

    const unmountEffects = pendingPassiveHookEffectsUnmount;
    pendingPassiveHookEffectsUnmount = [];
    for (let i = 0; i < unmountEffects.length; i += 2) {
      const effect = ((unmountEffects[i]: any): HookEffect);
      const fiber = ((unmountEffects[i + 1]: any): Fiber);
      const destroy = effect.destroy;
      effect.destroy = undefined;
    
      if (typeof destroy === 'function') {
        try {
          destroy();//销毁函数执行
        } catch (error) {
          captureCommitPhaseError(fiber, error);
        }
      }
    }
    
    const mountEffects = pendingPassiveHookEffectsMount;
    pendingPassiveHookEffectsMount = [];
    for (let i = 0; i < mountEffects.length; i += 2) {
      const effect = ((mountEffects[i]: any): HookEffect);
      const fiber = ((mountEffects[i + 1]: any): Fiber);
      
      try {
        const create = effect.create;//本次render的创建函数
       effect.destroy = create();
      } catch (error) {
        captureCommitPhaseError(fiber, error);
      }
    }
    
    

useRef

sring类型的ref已经不在推荐使用,ForwardRef只是把ref通过传参传下去,createRef也是{current: any这种结构,所以我们只讨论function或者{current: any}的useRef
//createRef返回{current: any}
export function createRef(): RefObject {
  const refObject = {
    current: null,
  };
  return refObject;
}
//ForwardRef第二个参数是ref对象
let children = Component(props, secondArg);
  • 声明阶段

    和其他hook一样

    export function useRef<T>(initialValue: T): {|current: T|} {
      const dispatcher = resolveDispatcher();
      return dispatcher.useRef(initialValue);
    }
    
  • mount阶段

    mount时会调用mountRef,创建hook和ref对象。

    function mountRef<T>(initialValue: T): {|current: T|} {
      const hook = mountWorkInProgressHook();//获取useRef
      const ref = {current: initialValue};//ref初始化
      hook.memoizedState = ref;
      return ref;
    }
    

    render阶段:将带有ref属性的Fiber标记上Ref Tag,在一步发生在beginWork和completeWork函数中的markRef

    export const Ref = /*                          */ 0b0000000010000000;
    
    //beginWork中
    function markRef(current: Fiber | null, workInProgress: Fiber) {
      const ref = workInProgress.ref;
      if (
        (current === null && ref !== null) ||
        (current !== null && current.ref !== ref)
      ) {
        workInProgress.effectTag |= Ref;
      }
    }
    //completeWork中
    function markRef(workInProgress: Fiber) {
      workInProgress.effectTag |= Ref;
    }
    

    commit阶段:

      会在commitMutationEffects函数中判断ref是否改变,如果改变了会先执行commitDetachRef先删除之前的ref,然后在commitLayoutEffect中会执行commitAttachRef赋值ref。
    
    function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        // ...
        
        if (effectTag & Ref) {
          const current = nextEffect.alternate;
          if (current !== null) {
            commitDetachRef(current);//移除ref
          }
        }
      }
    
    function commitDetachRef(current: Fiber) {
      const currentRef = current.ref;
      if (currentRef !== null) {
        if (typeof currentRef === 'function') {
          currentRef(null);//类型是function,则调用
        } else {
          currentRef.current = null;//否则赋值{current: null}
        }
      }
    }
    
    
    function commitAttachRef(finishedWork: Fiber) {
      const ref = finishedWork.ref;
      if (ref !== null) {
        const instance = finishedWork.stateNode;//获取ref的实例
        let instanceToUse;
        switch (finishedWork.tag) {
          case HostComponent:
            instanceToUse = getPublicInstance(instance);
            break;
          default:
            instanceToUse = instance;
        }
    
        if (typeof ref === 'function') {//ref赋值
          ref(instanceToUse);
        } else {
          ref.current = instanceToUse;
        }
      }
    }
    
    
  • update阶段

    update时调用updateRef获取获取当前useRef,然后返回hook链表

    function updateRef<T>(initialValue: T): {|current: T|} {
      const hook = updateWorkInProgressHook();//获取当前useRef
      return hook.memoizedState;//返回hook链表
    }
    

useMemo&useCallback

  • 声明阶段

    和其他hook 一样

  • mount阶段

    mount阶段useMemo和useCallback唯一区别是在memoizedState中存贮callback还是callback计算出来的函数

    function mountMemo<T>(
      nextCreate: () => T,
      deps: Array<mixed> | void | null,
    ): T {
      const hook = mountWorkInProgressHook();//创建hook
      const nextDeps = deps === undefined ? null : deps;
      const nextValue = nextCreate();//计算value
      hook.memoizedState = [nextValue, nextDeps];//把value和依赖保存在memoizedState中
      return nextValue;
    }
    
    function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
      const hook = mountWorkInProgressHook();//创建hook
      const nextDeps = deps === undefined ? null : deps;
      hook.memoizedState = [callback, nextDeps];//把callback和依赖保存在memoizedState中
      return callback;
    }
    
  • update阶段

    update时也一样,唯一区别就是直接用回调函数还是执行回调后返回的value作为[?, nextDeps]赋值给memoizedState

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();//获取hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {//浅比较依赖
        return prevState[0];//没变 返回之前的状态
      }
    }
  }
  const nextValue = nextCreate();//有变化重新调用callback
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();//获取hook
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;

  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {//浅比较依赖
        return prevState[0];//没变 返回之前的状态
      }
    }
  }

  hook.memoizedState = [callback, nextDeps];//变了重新将[callback, nextDeps]赋值给memoizedState
  return callback;
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 使用React Hooks有什么优势? 什么是hookshook 是一些可以让你在函数组件里面钩入react st...
    Lyan_2ab3阅读 355评论 0 1
  • React Hooks 原理[https://github.com/brickspert/blog/issues/...
    Yong_bcf4阅读 766评论 0 1
  • 原文地址React Hooks实例教学 目前,Hooks 应该是 React 中最火的概念了,在阅读这篇文章之前,...
    DC_er阅读 15,789评论 4 14
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,562评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,209评论 1 3