# hooks
# 前言
React-hooks实际就是函数组件解决没有 state,生命周期,逻辑不能复用的一种技术方案。
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
- 在无状态组件每一次函数上下文执行的时候,react用什么方式记录了hooks的状态?
- 多个react-hooks用什么来记录每一个hooks的顺序的 ? 换个问法!为什么不能条件语句中,声明hooks? hooks声明为什么在组件的最顶部?
- function函数组件中的useState,和 class类组件 setState有什么区别?
- react 是怎么捕获到hooks的执行上下文,是在函数组件内部的?
- useEffect,useMemo 中,为什么useRef不需要依赖注入,就能访问到最新的改变值?
- useMemo是怎么对值做缓存的?如何应用它优化性能?
- 为什么两次传入useState的值相同,函数组件不更新?
# 一 初识:揭开hooks的面纱
# 当我们引入hooks时候发生了什么?
我们从引入 hooks
开始,以useState
为例子,当我们从项目中这么写:
import { useState } from 'react'
在React.js
中可以看到Hooks导入的代码:
import {
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
} from './ReactHooks';
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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
以上就是三个最常用的Hooks
在React
中的源码,可见他们也跟其他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;
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),
);
}
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
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
看来对于第一次渲染组件,和更新组件,react-hooks
采用了两套Api
我们用流程图来描述整个过程:
# 二 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> }
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];
}
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
对象的 memoizedState
和baseState
属性,然后创建一个queue
对象,里面保存了负责更新的信息。
useState
和useReducer
触发函数更新的方法都是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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mountWorkInProgressHook
这个函数做的事情很简单,首先每次执行一个hooks
函数,都产生一个hook
对象,里面保存了当前hook
信息,然后将每个hooks
以链表形式串联起来,并赋值给workInProgress
的memoizedState
。也就证实了上述所说的,函数组件用memoizedState
存放hooks
链表。
至于hook
对象中都保留了那些信息?
memoizedState: useState中
保存 state
信息 | useEffect
中 保存着 effect
对象 | useMemo
中 保存的是缓存的值和 deps
| useRef
中保存的是 ref
对象。
baseQueue : usestate
和useReducer
中 保存最新的更新队列。
baseState : usestate
和useReducer
中,一次更新中 ,产生的最新state
值。
queue : 保存待更新队列 pendingQueue
,更新函数 dispatch
等信息。
next: 指向下一个 hooks
对象。
那么当我们函数组件执行之后,四个hooks
和workInProgress
将是如图的关系。
知道每个hooks
关系之后,我们应该理解了,为什么不能条件语句中,声明hooks
。
我们用一幅图表示如果在条件语句中声明会出现什么情况发生。
如果我们将上述demo
其中的一个 useRef
放入条件语句中,
let curRef = null
if(isFisrt){
curRef = useRef(null)
}
2
3
4
因为一旦在条件语句中声明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);
}
}
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
对象是否处于渲染阶段,如果处于渲染阶段,那么不需要我们在更新当前函数组件,只需要更新一下当前update
的expirationTime
即可。
如果当前fiber
没有处于更新阶段。那么通过调用lastRenderedReducer
获取最新的state
,和上一次的currentState
,进行浅比较,如果相等,那么就退出,这就证实了为什么useState
,两次值相等的时候,组件不渲染的原因了,这个机制和Component
模式下的setState
有一定的区别。
如果两次state
不相等,那么调用scheduleUpdateOnFiber
调度渲染当前fiber
,scheduleUpdateOnFiber
是react
渲染更新的主要函数。
也就是我们每次执行dispatchAction方法,比如setAge或setName。就会创建一个保存着此次更新信息的update对象,添加到更新链表queue上。然后每个Hooks节点就会有自己的一个queue。比如假设我们执行了下面几个语句:
setAge(19);
setAge(20);
setAge(21);
2
3
那么我们的Hooks链表就会变成这样:
在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,
);
}
}
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,
);
}
2
3
4
5
6
7
8
9
10
11
每个hooks
初始化都会创建一个hook
对象,然后将hook的memoizedState
保存当前effect hook
信息。
有两个memoizedState
大家千万别混淆了
workInProgress / current
树上的memoizedState
保存的是当前函数组件每个hooks
形成的链表。- 每个
hooks
上的memoizedState
保存了当前hooks
信息,不同种类的hooks
的memoizedState
内容不同。上述的方法最后执行了一个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;
}
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
,就是workInProgress
的updateQueue
。然后将effect
放入updateQueue
中。
假设我们在一个函数组件中这么写:
useEffect(()=>{
console.log(1)
},[ props.a ])
useEffect(()=>{
console.log(2)
},[])
useEffect(()=>{
console.log(3)
},[])
2
3
4
5
6
7
8
9
10
最后workInProgress.updateQueue
会以这样的形式保存:
也就是在mount阶段我们所有的effect都以链表的形式被挂载到了fiberNode上。然后在组件渲染完毕之后,React就会执行updateQueue中的所有方法。
# 初始化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;
}
2
3
4
5
6
7
8
9
10
11
就是创建一个hook
,然后执行useMemo
的第一个参数,得到需要缓存的值,然后将值和deps
记录下来,赋值给当前hook
的memoizedState
。整体上并没有复杂的逻辑。
# 初始化useRef -> mountRef
对于useRef
初始化处理,更是简单,
function mountRef<T>(initialValue: T): {|current: T|} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
2
3
4
5
6
mountRef
初始化很简单, 创建一个ref对象, 对象的current
属性来保存初始化的值,最后用memoizedState
保存ref
,完成整个操作。
# mounted 阶段 hooks 总结
初始化阶段,react-hooks
做的事情,在一个函数组件第一次渲染执行上下文过程中,每个react-hooks
执行,都会产生一个hook
对象,并形成链表结构,绑定在workInProgress
的memoizedState
属性上,然后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;
}
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
执行过程中,执行了多次函数组件最后复制
current
的hooks
,把它赋值给workInProgressHook
,用于更新新的一轮hooks
状态。
# updateState
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
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];
}
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];
}
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;
}
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,
);
}
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;
}
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
改变,重新执行,就不会出现问题了。
# 一次点击事件更新
← React的生命周期函数 NVM安装教程 →