为什么需要 hook
React 文档中也有说明了 Hooks 的提出主要是为了解决什么问题的:
组件之间复用状态逻辑很难。
就以前 React 为了将一个组件的逻辑抽离复用,不和渲染代码混用在一个 class 的做法,比较推介的是用高阶组件,将状态逻辑抽离出来,通过不同的样式组件传入带有状态逻辑的高阶组件中,增强样式组件的功能,从而达到复用逻辑的功能。再早期就会使用 mixin 去实现。
缺点:增加嵌套层级,代码会更加难以理解。
复杂组件变得难以理解
在使用 class 组件的时候,我们少不了在生命周期函数中添加一些操作,例如调用一些函数,或者去发起请求,在销毁组件的时候为了防止内存溢出,我们可能还需要对一些事件移除。那么这个时候我们就需要在componentDidMount,componentDidUpdate 中可能会调用相同的函数获取数据,componentWillUnmount 中移除事件等;这些因为和组件有很强的耦合性,也很难通过高阶组件的方式抽离出来,而 Hook 将组件中关联的部分拆分成更小的函数,每个函数功能更加单一。
难以理解的 class
Hook 就是一个以纯函数的方式存在的class组件;以前我们使用纯函数组件时都有一个标准,就是这个组件并不具备自身的生命周期使用,以及自己独立的state。只是单纯的返回一个组件。或者是根据传入的 props 组装组件。但随着 Hook 的发布,React 团队是想将 React 更加偏向函数式编程的方式编写组件,让本来存函数组件变得可以使用 class 组件的一些特性。
hook 调用入口
在 hook 源码中 hook 存在于 Dispatcher 中,Dispatcher 就是一个对象,不同 hook 调用的函数不一样;在 renderWithHooks 函数中,在 FunctionComponent render 前, 会根据 FunctionComponent 对应 fiber 的以下条件区分 mount 与 update。
current === null || current.memoizedState === null
并将不同情况对应的dispatcher赋值给全局变量ReactCurrentDispatcher的current属性。
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
// mount 时的 Dispatcher
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
...
}
// mount 时的 Dispatcher
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
...
};
在 FunctionComponent render 时,会从 ReactCurrentDispatcher.current(即当前 dispatcher)中寻找需要的hook。换言之,不同的调用栈上下文为 ReactCurrentDispatcher.current 赋值不同的 dispatcher,则FunctionComponent render 时调用的 hook 也是不同的函数。
Hook的数据结构
通过 mountWorkInProgressHook 函数进行创建 hook 的数据结构:
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null, // 对于不同 hook,有不同的值
baseState: null, // 初始 state
baseQueue: null, // 初始 queue 队列
queue: null, // 需要更新的 update
next: null, // 下一个 hook
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
这里除了 memoizedState 字段外,其他字段与之前介绍的 updateQueue 的数据结构字段意义类似。
hook 链表结构
上面创建的 hook 结构中,通过 next 指针,实现了一个无环的单向链表。产生的 hook 对象依次排列,形成链表存储到函数组件 fiber.memoizedState 上。
{
baseQueue: null,
baseState: 'hook1',
memoizedState: null,
queue: null,
next: {
baseQueue: null,
baseState: null,
memoizedState: 'hook2',
next: null
queue: null
}
}
memoizedState
下面来看下不同的 hook 中 memoizedState 对应的值: