React hooks实战总结
深入源码分析hooks,这里我们以刚使用过的hook useState
为例,看看他是怎么管理我们的FC state的。
export function useState(initialState: (() => S) | S) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
这个函数接收一个参数 initialState: (() => S) | S
,初始state的函数或者我们的state初始值。
然后调用
<code>dispatcher.useState(initialState);</code>
,这里我们看一下 dispatcher
是怎么来的:
function resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; ... return dispatcher; }
发现是通过 ReactCurrentDispatcher.current
得到,那 ReactCurrentDispatcher
又是何方神圣呢?
我们进一步看看它怎么来的
<pre>import type {Dispatcher} from 'react-reconciler/src/ReactFiberHooks';
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
根据type,我们可以判断dispatcher的类型是 react-reconciler/src/ReactFiberHooks
里面定义的 Dispatcher
,可以看到这个current属性是个null。那它是什么时候被赋值的呢?
我们来看看functionComponent的render过程 renderWithHooks
,
export function renderWithHooks( current: Fiber | null, workInProgress: Fiber, Component: any, props: any, refOrContext: any, nextRenderExpirationTime: ExpirationTime, ): any{ .... if (__DEV__) { ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV; } else { ReactCurrentDispatcher.current = nextCurrentHook === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; } }
这里react源码根据 nextCurrentHook
做了一些判断,我移除掉了,只关注 ReactCurrentDispatcher.current
的值,可以看到它的取值分为两种, HooksDispatcherOnMount
和 HooksDispatcherOnUpdate
分别对应 mount/update
两个组件状态;这里我们先看
HooksDispatcherOnMount
:
const HooksDispatcherOnMount: Dispatcher = { ... useState: mountState, ... };
这就是我们寻寻觅觅的 Dispatcher
的长相,最终我们 useState
在组件mount的时候执行的就是这个
mountState
了,那我们就迫不及待如饥似渴的来看看 mountState
又做了什么吧。
function mountState( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction>] { const hook = mountWorkInProgressHook(); if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState; const queue = (hook.queue = { last: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), }); const dispatch: Dispatch< BasicStateAction, > = (queue.dispatch = (dispatchAction.bind( null, // Flow doesn't know this is non-null, but we do. ((currentlyRenderingFiber: any): Fiber), queue, ): any)); return [hook.memoizedState, dispatch]; }
进入这个函数首先执行的
mountWorkInProgressHook()
获取到当前的 workInProgressHook
,看这个名字就知道他是和 workInProgress
分不开了,这个 workInProgress
代表了当前正在处理的fiber,
fiber
是当前组件的需要完成或者已经完成的work的对象,也可以理解为我们的这个正在执行mountState的组件的各种数据和状态的集合。我们来具体的看一下mountWorkInProgressHook的执行逻辑:
function mountWorkInProgressHook(): Hook { const hook: Hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list firstWorkInProgressHook = workInProgressHook = hook; } else { // Append to the end of the list workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; }
判断当前 fiber
的 workInProgressHook
是不是 null
,如果是,将全新的hook赋值给全局的 workInProgressHook
和 firstWorkInProgressHook
,否则,将初始值赋值给 workInProgressHook
。相当于 mountState
里面的hook值就是
const hook: Hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, };
实际上,workInProgressHook是这样的一个链表结构,React里面广泛使用了这样的结构存储副作用。
<pre>{
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: {
...
next: {
...
next: {
next: {...},
...
},
},
}
}
继续往下看:
if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState;
useState接收的参数类型如果是函数,这里就会执行传进来的函数获取 initialState
,赋值给 hook.memoizedState = hook.baseState
这两个属性,再往下,建立了当前 hook
的更新队列 queue:
,这个我们后续再讲,这里暂时不用知道。继续往下看,是我们修改state的回调函数,通常是setState,通过改变 dispatchAction
的this指向,将当前render的fiber和上面创建的queue作为参数传入,当我们执行 setState
的时候实际上调用的就是这里的 dispatchAction
,最后一行:
return [hook.memoizedState, dispatch];
将 state
和 setState
以数组的形式返回,这也是我们使用 useState hook
的正确姿势。到这里相信大家都很清楚了, useState
通过将我们的初始 state
暂存到 workInProgressHook
的 memoizedState
中,每次更新的时候通过 dispatchAction
更新 workInProgressHook
。
type UpdateQueue= { last: Update| null, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, lastRenderedState: S | null, };
看到这个结构,熟悉 react fiber
的同学已经心中有数了,它的last属性是一个链表,用来存储当前hook的变化信息,能够通过
next
迭代处理所有变更信息和状态。这里我们就到此为止,感兴趣的同志可以自行深入琢磨,对于这个 hook
,掌握到这里已经够了,很多文章说 useState
和 useReducer
的基友关系,从这里我们就看出来了, useState
最终使用的也是 useReducer
一致的api,通过类似 redux
的理念,通过 dispatchAction
修改 state
,有兴趣的同志可以看这里
useReducer
源码;