# hooks

# 前言

React-hooks实际就是函数组件解决没有 state,生命周期,逻辑不能复用的一种技术方案。

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

  1. 在无状态组件每一次函数上下文执行的时候,react用什么方式记录了hooks的状态?
  2. 多个react-hooks用什么来记录每一个hooks的顺序的 ? 换个问法!为什么不能条件语句中,声明hooks? hooks声明为什么在组件的最顶部?
  3. function函数组件中的useState,和 class类组件 setState有什么区别?
  4. react 是怎么捕获到hooks的执行上下文,是在函数组件内部的?
  5. useEffect,useMemo 中,为什么useRef不需要依赖注入,就能访问到最新的改变值?
  6. useMemo是怎么对值做缓存的?如何应用它优化性能?
  7. 为什么两次传入useState的值相同,函数组件不更新?

大纲.jpg

# 一 初识:揭开hooks的面纱

# 当我们引入hooks时候发生了什么?

我们从引入 hooks开始,以useState为例子,当我们从项目中这么写:

import { useState } from 'react'
1

React.js中可以看到Hooks导入的代码:

import {
  useCallback,
  useContext,
  useEffect,
  useImperativeMethods,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from './ReactHooks';
1
2
3
4
5
6
7
8
9
10
11

那么我们就来看看具体的代码是怎样的:

function resolveDispatcher() {
  const dispatcher = ReactCurrentOwner.currentDispatcher;
  return dispatcher;
}

export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

以上就是三个最常用的HooksReact中的源码,可见他们也跟其他React的API一样,只管定义,不管实现。他们都调用了ReactCurrentOwner.currentDispatcher.xxx对应的方法。那么这个ReactCurrentOwner.currentDispatcher是啥呢?

react/src/ReactCurrentDispatcher.js

import type { Dispacther } from 'react-reconciler/src/ReactFiberHooks'; 

const ReactCurrentDispatcher = {  current: (null: null | Dispatcher), }; 

export default ReactCurrentDispatcher;
1
2
3
4
5

好吧,它继续将我们带向 react-reconciler/src/ReactFiberHooks.js这个文件。那么我们继续前往这个文件。

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return useReducer(
    basicStateReducer,
    // useReducer has a special case to support lazy useState initializers
    (initialState: any),
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

兜兜转转我们终于清楚了React Hooks 的源码就放 react-reconciler/src/ReactFiberHooks.js 目录下面。 在这里如上图所示我们可以看到有每个Hooks的类型定义。同时我们也可以看到Hooks的具体实现,大家可以多看看这个文件。首先我们注意到,我们大部分的Hooks都有两个定义:

// react-reconciler/src/ReactFiberHooks.js 
// Mount 阶段Hooks的定义 
const HooksDispatcherOnMount: Dispatcher = {  
  useEffect: mountEffect,  
  useReducer: mountReducer,  
  useState: mountState, 
  // 其他Hooks 
}; 
// Update阶段Hooks的定义 
const HooksDispatcherOnUpdate: Dispatcher = {  
  useEffect: updateEffect,  
  useReducer: updateReducer,  
  useState: updateState,  
 // 其他Hooks 
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

看来对于第一次渲染组件,和更新组件,react-hooks采用了两套Api

我们用流程图来描述整个过程:

17AC0A26-745A-4FD8-B91B-7CADB717234C.jpg

# 二 hooks初始化,我们写的hooks会变成什么样子

我们先写一个组件,并且用到上述四个主要hooks

import React , { useEffect , useState , useRef , useMemo  } from 'react' 
function Index(){   
  const [ number , setNumber ] = useState(0)    
  const DivDemo = useMemo(() => <div> hello , i am useMemo </div>,[])    
  const curRef  = useRef(null)    
  useEffect(()=>{       
    console.log(curRef.current)    
  },[])    
  return <div ref={ curRef } >        
    hello,world { number }         
    { DivDemo }        
    <button onClick={() => setNumber(number+1) } >number++</button>     
  </div> }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 初始化useState -> mountState

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
     // 如果 useState 第一个参数为函数,执行函数得到state
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  // 存放更新对象的链表
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null, // 带更新的
    interleaved: null, // 负责更新函数
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer, //用于得到最新的 state ,
    lastRenderedState: (initialState: any), // 最后一次得到的 state
  };
  hook.queue = queue;
  // 返回一个dispatch方法用来修改状态,并将此次更新添加update链表中
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchSetState.bind( // 负责更新的函数
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

首先会得到初始化的state,将它赋值给mountWorkInProgressHook产生的hook对象的 memoizedStatebaseState属性,然后创建一个queue对象,里面保存了负责更新的信息。

useStateuseReducer触发函数更新的方法都是dispatchAction,useState,可以看成一个简化版的useReducer,至于dispatchAction怎么更新state,更新组件的,我们接着往下研究dispatchAction

# mountWorkInProgressHook

在组件初始化的时候,每一次hooks执行,如useState(),useRef(),都会调用mountWorkInProgressHook,mountWorkInProgressHook到底做了写什么,让我们一起来分析一下:

// react-reconciler/src/ReactFiberHooks.js 
function mountWorkInProgressHook(): Hook {  
  const hook: Hook = {    
    memoizedState: null,    
    baseState: null,    
    queue: null,    
    baseUpdate: null,    
    next: null,  
  };  
  if (workInProgressHook === null) {    
    // 当前workInProgressHook链表为空的话,    
    // 将当前Hook作为第一个Hook    
    firstWorkInProgressHook = workInProgressHook = hook;  } 
  else {    
    // 否则将当前Hook添加到Hook链表的末尾    
    workInProgressHook = workInProgressHook.next = hook;  }  
  return workInProgressHook; 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

mountWorkInProgressHook这个函数做的事情很简单,首先每次执行一个hooks函数,都产生一个hook对象,里面保存了当前hook信息,然后将每个hooks以链表形式串联起来,并赋值给workInProgressmemoizedState。也就证实了上述所说的,函数组件用memoizedState存放hooks链表。

至于hook对象中都保留了那些信息?

memoizedStateuseState中 保存 state 信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和 depsuseRef 中保存的是 ref 对象。

baseQueue : usestateuseReducer中 保存最新的更新队列。

baseStateusestateuseReducer中,一次更新中 ,产生的最新state值。

queue : 保存待更新队列 pendingQueue ,更新函数 dispatch 等信息。

next: 指向下一个 hooks对象。

那么当我们函数组件执行之后,四个hooksworkInProgress将是如图的关系。

shunxu.jpg

知道每个hooks关系之后,我们应该理解了,为什么不能条件语句中,声明hooks

我们用一幅图表示如果在条件语句中声明会出现什么情况发生。

如果我们将上述demo其中的一个 useRef 放入条件语句中,

let curRef  = null
 if(isFisrt){
  curRef = useRef(null)
 }
1
2
3
4

hoo11.jpg

因为一旦在条件语句中声明hooks,在下一次函数组件更新,hooks链表结构,将会被破坏,current树的memoizedState缓存hooks信息,和当前workInProgress不一致,如果涉及到读取state等操作,就会发生异常。

# dispatchAction

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {

  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);
 	/* 创建一个update */
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null: any),
  };

  // 将更新附加到列表的末尾。
  const pending = queue.pending;
  if (pending === null) { // 证明第一次更新
    // This is the first update. Create a circular list.
    // 链表为空,将当前更新作为第一个,并保持循环
    update.next = update;
  } else {
    // 在最新的update对象后面插入新的update对象
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  const alternate = fiber.alternate;
  // 判断当前是否在渲染阶段
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 这是一个渲染阶段更新。 将其存储在延迟创建的队列映射 -> 更新链接列表中。 
    // 在此渲染过程之后,将重新启动并在工作中的钩子顶部应用隐藏的更新。
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // 队列当前是空的,这意味着我们可以在进入渲染阶段之前急切地计算下一个状态。 
      // 如果新状态与当前状态相同,直接退出。
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
          const currentState: S = (queue.lastRenderedState: any);/* 上一次的state */
          const eagerState = lastRenderedReducer(currentState, action);/* 最新的state */
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) { // 进行浅比较,如果相等,那么就退出
            return;
          }
        }
      }
    }

  if (enableSchedulingProfiler) {
    markStateUpdateScheduled(fiber, lane);
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

无论是类组件调用setState,还是函数组件的dispatchAction ,都会产生一个 update对象,里面记录了此次更新的信息,然后将此update放入待更新的pending队列中,dispatchAction第二步就是判断当前函数组件的fiber对象是否处于渲染阶段,如果处于渲染阶段,那么不需要我们在更新当前函数组件,只需要更新一下当前updateexpirationTime即可。

如果当前fiber没有处于更新阶段。那么通过调用lastRenderedReducer获取最新的state,和上一次的currentState,进行浅比较,如果相等,那么就退出,这就证实了为什么useState,两次值相等的时候,组件不渲染的原因了,这个机制和Component模式下的setState有一定的区别。

如果两次state不相等,那么调用scheduleUpdateOnFiber调度渲染当前fiberscheduleUpdateOnFiberreact渲染更新的主要函数。

也就是我们每次执行dispatchAction方法,比如setAge或setName。就会创建一个保存着此次更新信息的update对象,添加到更新链表queue上。然后每个Hooks节点就会有自己的一个queue。比如假设我们执行了下面几个语句:

setAge(19); 
setAge(20); 
setAge(21);
1
2
3

那么我们的Hooks链表就会变成这样:

updateQueue

在Hooks节点上面,会如上图那样,通过链表来存放所有的历史更新操作。以便在update阶段可以通过这些更新获取到最新的值返回给我们。这就是在第一次调用useState或useReducer之后,每次更新都能返回最新值的原因

# 初始化useEffect -> mountEffect

# mountEffect

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if (__DEV__) {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if (typeof jest !== 'undefined') {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
    }
  }

  if (__DEV__ && enableDoubleInvokingEffects) {
    return mountEffectImpl(
      MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
  } else {
    return mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# mountEffectImpl

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    undefined,
    nextDeps,
  );
}
1
2
3
4
5
6
7
8
9
10
11

每个hooks初始化都会创建一个hook对象,然后将hook的memoizedState保存当前effect hook信息。

有两个memoizedState大家千万别混淆了

  • workInProgress / current 树上的 memoizedState 保存的是当前函数组件每个hooks形成的链表。
  • 每个hooks上的memoizedState 保存了当前hooks信息,不同种类的hooksmemoizedState内容不同。上述的方法最后执行了一个pushEffect,我们一起看看pushEffect做了些什么?

# pushEffect


function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,
    create,
    destroy,
    deps,
    // Circular
    next: (null: any),
  };
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
   // componentUpdateQueue 会被挂载到fiberNode的updateQueue上
  if (componentUpdateQueue === null) { // 如果是第一个 useEffect
    // 将当前effect作为第一个节点
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    // 保持循环
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else { // 存在多个effect
    添加到当前的Queue链表中
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

首先创建一个 effect ,判断组件如果第一次渲染,那么创建 componentUpdateQueue ,就是workInProgressupdateQueue。然后将effect放入updateQueue中。

假设我们在一个函数组件中这么写:

useEffect(()=>{
    console.log(1)
},[ props.a ])
useEffect(()=>{
    console.log(2)
},[])
useEffect(()=>{
    console.log(3)
},[])

1
2
3
4
5
6
7
8
9
10

最后workInProgress.updateQueue会以这样的形式保存:

7B8889E7-05B3-4BC4-870A-0D4C1CDF6981.jpg

也就是在mount阶段我们所有的effect都以链表的形式被挂载到了fiberNode上。然后在组件渲染完毕之后,React就会执行updateQueue中的所有方法。

useEffectInFiberNode1

# 初始化useMemo -> mountMemo

相比其他 useState , useEffect等,它的逻辑实际简单的很。


function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
1
2
3
4
5
6
7
8
9
10
11

就是创建一个hook,然后执行useMemo的第一个参数,得到需要缓存的值,然后将值和deps记录下来,赋值给当前hookmemoizedState。整体上并没有复杂的逻辑。

# 初始化useRef -> mountRef

对于useRef初始化处理,更是简单,

function mountRef<T>(initialValue: T): {|current: T|} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}
1
2
3
4
5
6

mountRef初始化很简单, 创建一个ref对象, 对象的current 属性来保存初始化的值,最后用memoizedState保存ref,完成整个操作。

# mounted 阶段 hooks 总结

初始化阶段,react-hooks做的事情,在一个函数组件第一次渲染执行上下文过程中,每个react-hooks执行,都会产生一个hook对象,并形成链表结构,绑定在workInProgressmemoizedState属性上,然后react-hooks上的状态,绑定在当前hooks对象的memoizedState属性上。对于effect副作用钩子,会绑定在workInProgress.updateQueue上,等到commit阶段,dom树构建完成,在执行每个 effect 副作用钩子。

# hooks更新阶段

函数组件每次更新,每一次react-hooks函数执行,都需要有一个函数去做上面的操作,这个函数就是updateWorkInProgressHook,我们接下来一起看这个updateWorkInProgressHook

function updateWorkInProgressHook(): Hook {

  let nextCurrentHook: null | Hook;
  if (currentHook === null) {/* 如果 currentHook = null 证明它是第一个hooks */
    //首先如果是第一次执行hooks函数,那么从current树上取出memoizedState ,也就是旧的hooks。
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {/* 不是第一个hooks,那么指向下一个 hooks */
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {//第一次执行hooks
    //当函数组件更新也是调用 renderWithHooks ,memoizedState属性是置空的
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
     /* 这个情况说明 renderWithHooks 执行 过程发生多次函数组件的执行 ,暂时先不管 */
    // There's already a work-in-progress. Reuse it.
    // 已经有一项正在进行的工作。 重复使用它。
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.

    invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
   // if (!(nextCurrentHook !== null)) {
   //    {
   //     throw Error( "Rendered more hooks than during the previous render." );
   //  }
   //}
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

这一段的逻辑大致是这样的:

  • 首先如果是第一次执行hooks函数,那么从current树上取出memoizedState ,也就是旧的hooks

  • 然后声明变量nextWorkInProgressHook,这里应该值得注意,正常情况下,一次renderWithHooks执行,workInProgress上的memoizedState会被置空,hooks函数顺序执行,nextWorkInProgressHook应该一直为null,那么什么情况下nextWorkInProgressHook不为null,也就是当一次renderWithHooks执行过程中,执行了多次函数组件

  • 最后复制currenthooks,把它赋值给workInProgressHook,用于更新新的一轮hooks状态。

# updateState

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}
1
2
3
4
5
function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  invariant(
    queue !== null,
    'Should have a queue. This is likely a bug in React. Please file an issue.',
  );

  queue.lastRenderedReducer = reducer;

  const current: Hook = (currentHook: any);

  // The last rebase update that is NOT part of the base state.
  let baseQueue = current.baseQueue;

  // The last pending update that hasn't been processed yet.
  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    // 第一步:将 pending  queue 合并到 basequeue
    // We have new updates that haven't been processed yet.
    // We'll add them to the base queue.
    if (baseQueue !== null) {
      // Merge the pending queue and the base queue.
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }

  if (baseQueue !== null) {
    // We have a queue to process.
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        //优先级不足
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          eagerReducer: update.eagerReducer,
          eagerState: update.eagerState,
          next: (null: any),
        };
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // Update the remaining priority in the queue.
        // TODO: Don't need to accumulate this. Instead, we can remove
        // renderLanes from the original lanes.
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        // This update does have sufficient priority.
				 //此更新确实具有足够的优先级。
        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            // This update is going to be committed so we never want uncommit
            // it. Using NoLane works because 0 is a subset of all bitmasks, so
            // this will never be skipped by the check above.
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        // Process this update.
        if (update.eagerReducer === reducer) {
          // If this update was processed eagerly, and its reducer matches the
          // current reducer, we can use the eagerly computed state.
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

简化一下:

function updateReducer(reducer, initialArg, init) {
  // 创建一个新的hook,带有dispatchAction创建的update
  var hook = updateWorkInProgressHook();
  var queue = hook.queue;

  queue.lastRenderedReducer = reducer;
  var current = currentHook;

  var baseQueue = current.baseQueue; 
  var pendingQueue = queue.pending;

  current.baseQueue = baseQueue = pendingQueue;
  
  if (baseQueue !== null) {
    // 从这里能看到之前讲的创建闭环链表插入update的好处了吧?直接next就能找到第一个update
    var first = baseQueue.next;
    var newState = current.baseState;

    var update = first;
    // 开始遍历update链表执行所有setState
    do {
      var updateLane = update.lane;
      ... //更新优先级判断
      // 假如我们这个update上有多个setState,在循环过程中,最终都会做合并操作
      var action = update.action;
      // 这里的reducer会判断action类型,下面讲
      newState = reducer(newState, action);

      update = update.next;
    } while (update !== null && update !== first);

    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }

  var dispatch = queue.dispatch;
  return [hook.memoizedState, dispatch];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

上面的更新中,会循环遍历update进行一个合并操作,只取最后一个setState的值,那直接取最后一个setState的值不是更方便吗?

这样做是不行的,因为setState入参可以是基础类型也可以是函数,这样的话就可以解释文章开头的那个例子了, 如果传入的是函数,它会依赖上一个setState的值来完成更新操作,下面的代码就是上面的循环中的reducer

function basicStateReducer(state, action) {
  return typeof action === 'function' ? action(state) : action;
}
1
2
3

# updateEffect

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)) {
        pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    destroy,
    nextDeps,
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

useEffect 做的事很简单,判断两次deps 相等,如果相等说明此次更新不需要执行,则直接调用 pushEffect,这里注意 effect的标签,hookEffectTag,如果不相等,那么更新 effect ,并且赋值给hook.memoizedState,这里标签是 HookHasEffect | hookEffectTag,然后在commit阶段,react会通过标签来判断,是否执行当前的 effect 函数。

# updateMemo

function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在组件更新过程中,我们执行useMemo函数,做的事情实际很简单,就是判断两次 deps是否相等,如果不想等,证明依赖项发生改变,那么执行 useMemo的第一个函数,得到新的值,然后重新赋值给hook.memoizedState,如果相等 证明没有依赖项改变,那么直接获取缓存的值。

不过这里有一点,值得注意,nextCreate()执行,如果里面引用了usestate等信息,变量会被引用,无法被垃圾回收机制回收,就是闭包原理,那么访问的属性有可能不是最新的值,所以需要把引用的值,添加到依赖项 dep 数组中。每一次dep改变,重新执行,就不会出现问题了。

# 一次点击事件更新

91A72028-3A38-4491-9375-0895F420B7CD.jpg