本系列文章总共三篇:javascript
React hooks api 是在 react 这个库里面定义的,咱们以 useState 为例:java
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
复制代码
咱们能够发现 hooks 的定义很是简单,只是获取了 dispatch 而后调用 dispatcher 对应的 useState 属性,其它 hooks 也是相似,好比 useEffect 是调用 dispatcher 的 useEffect 属性。react
接着咱们就须要看看 dispatcher 究竟是什么,经过查看 resolveDispatcher 咱们发现 dispatcher 指向的是 ReactCurrentDispatcher.current。git
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
invariant(
dispatcher !== null,
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
);
return dispatcher;
}
复制代码
经过全局搜索咱们发现 **ReactCurrentDispatcher.current **在 ReactFiberHooks.js 这个文件中被赋值,接下来咱们就来看看这个文件。github
通过搜索咱们发现 **ReactCurrentDispatcher.current **在 ReactFiberHooks.js 文件中被频繁赋值,其中最主要被赋值的地方就在 renderWithHooks 方法中,通过搜索我发现 renderWithHooks 在 ReactFiberBeginWork.js 这个文件中被屡次调用,若是你以前看过上一篇文档或是对 react 的更新流程的源码比较熟悉的话,你应该知道 ReactFiberBeginWork.js 文件对应着 beginWork 这个方法,在这个方法中会找出要更新的 fiber 对象并执行对应的更新方法。
通过搜索我找到了和 function component 相关的几个方法:updateFunctionComponent 和 mountIndeterminateComponent,这两个都是更新 function component,区别是第一次渲染的时候会调用 mountIndeterminateComponent,由于第一次还没法肯定是 function component 仍是 class component。api
mountIndeterminateComponent:
app
updateFunctionComponent:
ide
接下来咱们就来看看 renderWithHooks 到底作了什么。函数
经过上面的流程图,咱们发现 renderWithHooks 作了以下几件事:post
HooksDispatcherOnMount 对象中定义了各个 hooks api 在初次渲染中的实现
HooksDispatcherOnUpdate 对象中定义了各个 hooks api 在再次渲染中的实现
通过前面的讲述此时你应该知道 useState 最终调用的是 ReactCurrentDispatcher.current.useState 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被赋值为 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱们先来看一下 HooksDispatcherOnMount 中的实现。
[hook.memoizedState, dispatch]
updateState 内部调用了 updateReducer,updateRecucer 内部作了如下事情:
queue.lastRenderedReducer
为 basicStateReducerdispatchAction 就是 useState 返回的第二个参数
通过前面的讲述此时你应该知道 useEffect 和 useState 同样,最终调用的是 ReactCurrentDispatcher.current.useEffect 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被赋值为 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱们先来看一下 HooksDispatcherOnMount 中的实现。
HooksDispatcherOnMount 中 useEffect 指向的是 mountEffect,它又调用了 mountEffectImpl
function mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void {
return mountEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
deps,
);
}
复制代码
mountEffectImpl 作了如下事情:
在更新阶段会将 dispatcher 指向 HooksDispatcherOnUpdate,在 HooksDispatcherOnUpdate 中 useEffect 指向的是 updateEffect,它又调用了 updateEffectImpl。
function updateEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void {
return updateEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
create,
deps,
);
}
复制代码
updateEffectImpl 作了如下事情:
const effect: Effect = {
tag, // hookEffectTag
create, // useEffect 接收的第一个参数
destroy, // 在 mountEffect 中是 undefined
deps, // useEffect 接收的第二个参数
// Circular
next: (null: any), // 指向下一个 effect
};
复制代码
最终生成的 updateQueue 会在 commit 阶段的 commitLayoutEffects 中执行
详情能够看上一篇
还记得上面 mountEffectImpl 方法会将 UpdateEffect | PassiveEffect 设置到 fiber.effectTag 上,对于有 UpdateEffect 的 fiber 对象在 commitLayoutEffects 中会执行 commitLayoutEffectOnFiber 方法,它对应的就是 commitLifeCycles 方法,在该方法中对于 FunctionComponent 会执行 commitHookEffectList方法,传入 UnmountLayout, MountLayout, finishedWork
在该方法中会对传入的 finishedWork.updateQueue 上面的 effect 对象执行 unmount 和 mount,也就是调用 effect 对象上的 destroy 方法和 create 方法,对应于 useEffect 返回的方法和传入的方法,第一次渲染设置的 destroy 为 undefined 因此第一次渲染 destroy 不会执行
useRef 和其它 hooks 同样最终调用的是 ReactCurrentDispatcher.current.useRef 而 ReactCurrentDispatcher.current 又是在 renderWithHooks 中被赋值为 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate,那咱们先来看一下 HooksDispatcherOnMount 中的实现。
在 HooksDispatcherOnMount 中 useRef 指向的是 mountRef 方法,咱们来看一下它作了什么:
const ref = { current: initialValue };
初始值就是传入 useRef 的第一个参数hook.memoizedState = ref;
在 HooksDispatcherOnUpdate 中 useRef 指向的是 updateRef 方法,咱们来看一下它作了什么:
通过上面的几个 hook api 的实现咱们发现每一个 hook api 都须要先建立一个 hook 对象,而建立 hook 对象针对初次渲染和再次渲染这两个阶段调用的方法有所不一样,咱们先来看初次渲染。
初次渲染调用的是 mountWorkInProgressHook 方法,咱们来看一下它作了什么:
接下来咱们看看再次渲染时调用的 updateWorkInProgressHook 方法:
咱们来看看 hook 对象究竟是个什么东西
const hook: Hook = {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
复制代码
存储 hook 对象的数据,useState 对应的就是 state,useEffect 对应的就是 effect 对象,useRef 对应的就是 ref 对象
和 useState 相关,在初次渲染时等于传入的初始 state,后续是每次计算出的新的 state
相似于 fiber 对象的 updateQueue,每次调用 useState 返回的 setSomeState 方法就会建立一个 update 对象放到 queue 中,而后在 render 阶段再遍历 queue 计算出新的 state
const queue = (hook.queue = {
last: null, // 指向最后一个 update,它的 next 指向第一个 update,这是一个循环链表
dispatch: null, // dispatch 方法,用于计算出新的 state
lastRenderedReducer: reducer, // 最后一个 update 的 reducer
lastRenderedState: (initialState: any), // 指向最后一个 update 产生的 state
});
复制代码
包含带注释的源码、demos和流程图
github.com/kwzm/learn-…