react hooks在react 16.8版本推出后就广受好评,因为它很好的解决了旧版本react无法优雅的复用状态逻辑的问题,同时官方说明hooks会向后兼容不存在breaking changes,在项目中更好的无缝接入。
背景和意义
- 目前项目中hooks使用越来越普及,我们作为开发者不仅要知其然还要知其所以然
- 让我们在使用过程中能更快的定位排查问题、性能调优
- 学习和了解优秀框架的实现思路
从两个阶段出发分析
- 初次渲染
- 更新阶段
DEMO
我们以最基本的demo开始,其中涉及两个基本的hook:
useState和useEffect
一、初次渲染
核心流程
上图就是我们某一个hook组件在初次渲染时所经历的核心流程,大致分为三步:
组件类型解析(是Function、class类型),然后去执行对应类型的处理方法
- 当前fiberNode(也就是当前组件,react16引入了fiber概念)上进行hook的创建和挂载,将我们所有的hook api挂载到全局变量上(dispatcher)
- 顺序执行当前组件,每遇到一个hook api通过next把它连接到我们的当前组件的hook链表上
fiberNode结构
初次渲染完成后的当前fiberNode(组件)中的结构关系可以用下图表示:
源码一览
hook api挂载
初次渲染currentDispatcher为空,先挂载所有hook到当前fiberNode的dispatcher,其实也就是HooksDispatcherOnMountInDEV变量
{
if (currentDispatcher !== null) {
currentDispatcher = HooksDispatcherOnUpdateInDEV;
} else {
currentDispatcher = HooksDispatcherOnMountInDEV;
}
}
看看HooksDispatcherOnMountInDEV内部
发现正式我们熟悉的useState等各种原生hook,他们内部其实是调用的mountXXX方法
HooksDispatcherOnMountInDEV = {
useCallback: function (callback, deps) {
return mountCallback(callback, deps);
},
useEffect: function (create, deps) {
return mountEffect(create, deps);
},
useMemo: function (create, deps) {
return mountMemo(create, deps);
},
useState: function (initialState) {
return mountState(initialState);
}
}
回到我们的demo,首先是mountState
其实做了三件事:
- 创建当前hook的链表节点,节点的数据结构为上图红框。memorizedState是我们最终返回的初始值;queue其实是更新队列,当我们多次更新某一状态时需要用queue队列存取和遍历;next用来连接下一个hook
- 将当前hook连接到当前的fiberNode的hook链表上
- 绑定状态更新方法(dispatchAction),并返回[state,dispatchAction]
继续看demo,到useEffect,内部实际上执行mountEffectImpl方法
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
// 创建并获取当前hook节点信息
var hook = mountWorkInProgressHook();
hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
}
function mountWorkInProgressHook() {
// 将当前hook连接到我们的hook链表中
var hook = {
memoizedState: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function pushEffect(tag, create, destroy, deps) {
var effect = {
tag: tag, // 更新标识
create: create, // 传入的回调,也就是我们开发时的第一个参数
destroy: destroy, // return 的函数,组件销毁时执行的函数
deps: deps, // 依赖项数组
next: null
};
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
// 这里做的就是把每个useEffect hook单独以链式结构存到了componentUpdateQueue这个全局变量中
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var lastEffect = componentUpdateQueue.lastEffect;
var firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
return effect;
}
综上useEffect内部做了两件事:
- mountWorkInProgressHook方法,就是将当前hook连接到我们的fiberNode的hook链表中
- 定义effect对象存储我们传入的信息,同时将hook存入到componentUpdateQueue更新队列(这个队列是用来专门存储useEffect hook的)
至此我们初次渲染结束,我们此时fiberNode的hook链式结构为
// 当前fiber节点的内部hook链
currentFiber:{
...
memoizedState:{
memoizedState:xxx,
...
next:{
memoizedState:xxx,
...
next:{
memoizedState:xxx,
...
next:hook4
}
}
}
}
更直观一些看的话如下图所示