我只是不能忍受。。。。你一生在这里打转。你太聪明,太有趣。。。你只有一次生命,应该尽可能活的充实。react
基于reactV17.0源码分析数组
本片文章主要经过react的源码,分析useState、useEffect和两个钩子函数的内在执行原理。缓存
为了解决包括不限于以上的问题,咱们打开react项目研究一下源码。限于我的技术缘由,若有问题戳我。markdown
领略react-hook以前,咱们先回顾一下从reactDom.render(element, dom)
开始到hook
节点建立中间经历了那些步骤。下面是我根据源码,将初次渲染的关键路径简单的梳理了一下,只涉及关键函数。(打开react项目,点点点)闭包
renderWithHooks
顾名思义,这个函数就是对hook
组件的处理函数的入口。下面简单的分析一下这个函数主要作了什么:dom
currentlyRenderFiber
指向workInProgress
memoizedState
判断首次挂载/更新,获取hooks
方法集的Dispatcher
对象,并将此对象赋值给全局变量ReactCurrentDispatcher
的current
属性hook
组件的function
构造方法// packages/react-reconciler/src/ReactFiberHooks.old.js
export function renderWithHooks() {
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
...
//赋值全局变量,能够在react中使用相关hook方法
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
let children = Component(props, secondArg);
// 省略一些条件的判断,和置空
return children;
}
复制代码
react
模块中定义,并暴露,因此咱们能import { useState, useEffect } from 'react'
renderWithHooks
函数实现各类钩子函数,并将各个钩子函数挂在对应的fiber
对象上的hook
链表hook
的构造方法(Component()
)时,可以使用相关的hook
方法实现并挂载在全局变量ReactCurrentDispatcher
以后,在执行hook组件的构造方法时,咱们就能拿到相关的hook方法。咱们就先拿最多见的useState
和useEffect
来讲明,其余的钩子函数先不考虑。函数
// packages/react/src/ReactCurrentDispatcher.js
//在react模块中定义,并抛出了这个对象
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
//packages/react-reconciler/src/ReactFiberHooks.old.js
//在react-reconciler模块中实现
const HooksDispatcherOnMount: Dispatcher = {
//初次挂载
useState: mountState,
useEffect: mountEffect,
};
const HooksDispatcherOnUpdate: Dispatcher = {
//更新
useEffect: updateEffect,
useState: updateState,
};
复制代码
useState
源码里咱们能够看到,最关键的就是利用bind
闭包,缓存了currentlyRenderingFiber
对象(我也列出了源码中对此对象的解释,就是当前的fiber
节点)和queue
对象(保存的是更新对象update
,在dispatchAction
中咱们能够看到)源码分析
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber = (null: any);
...
function mountState(initialState,){
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
// 利用闭包缓存
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
复制代码
接下来咱们能够来看看hook对象的建立mountWorkInProgress
函,在这个函数里主要就是构造了一个hook的单项链表,并将workInProgressHook
指针指向当前hook
。对于workInProgressHook
对象,源码中也有详细的解释,用以保存将要加进当前fiber
对象的hook
链表优化
// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
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;
}
复制代码
咱们先来看一段实例代码ui
function MyName(){
const [name, setName] = useState()
return <div> <span>{name}</span> <button onClick={setName('骆家豪')}> 展现名字</button> </div>
}
复制代码
看了上面初次挂载的解析后,咱们明白setName
就是dispatchAction
函数,而且利用闭包缓存了当前fiber对象的指引。so,咱们来看看最最核心的dispatchAction
函数作了什么骚操做。
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) {
//调用优先级相关,可看以前的文章
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
//建立一个更新任务
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 将更新加到链表的最后
const pending = queue.pending;
if (pending === null) {
// 这是第一个更新,建立一个环形链表
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
...
//发起调度
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
复制代码
咱们能够看到其实就是构造了一个update
的链式结构,并挂在hook
对象上的queue
属性上,并在最后发起一个调度。那么调度以后是如何的从新计算fiber
节点的,如何处理queue
中的update
更新链,咱们还要看updateState
函数。
function updateState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
复制代码
直接返回一个updateReducer
函数,顾名思义,就是一个更新合并的函数。将一次或屡次setName
函数产生的update
合并。计算出最新的state
。
function updateReducer(){
//1.合并并计算queue队列
let baseQueue = current.baseQueue;
//2.若是存在等待的更新队列,则循环update的单向链表,reducer全部的state
if(baseQueue !== null){
const first = baseQueue.next;
let newState = current.baseState;
let update = first;
do {
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== null && update !== first)
}
//3.将最新的state存入当前hook,并返回
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
复制代码
到此咱们将useState
这个hook
的建立,更新过程过了一遍。下面来技术总计。
hook
对象以链表的形式保存在当前fiber
对象的memoizedState
属性上,造成环形结构。便于以后的便利合并。useState
钩子函数返回的第二个参数setXxx
,其实就是利用闭包,对应源码dispatchAction.bind(null, currentlyRenderingFiber,queue,)
。缓存了当前的fiber
对象和queue
跟新队列,这也解释了为何hook
函数不须要this
指针也能对应上指定的更新对象setXxx
时,也就是建立了一个update
,并将update
对象以单项链表的形式保存在当前hook
的queue
属性上。并在最后发起一个调度scheduleUpdateOnFiber
,全部更新的入口函数。memoizedState
,就会执行updateState
,也就是执行了updateReducer
函数,顾名思义就是合并更新updateReducer
,在这个函数中将update
合并,并do-while
遍历update
,合并计算update
对象中的action
属性,就是setXxx
的第一个参数,能够是函数。最后返回一个新的state
保存在当前hook的memoizedState
属性中。看完useState
的代码,在看useEffect
函数,大部分都是相同的。最大的不一样仍是触发时机的不一样,useEffect
在render
过程以后触发计算,useState
在下次计算当前fiber的时候触发执行计算newState
。
咱们先一块儿来看看 useEffect
初次挂载,和更新时的代码。
function mountEffect(create,deps) {
return mountEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
}
function updateEffect(create,deps) {
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
}
复制代码
看了代码其实区别就转换成了mountEffectImpl
和updateEffectImpl
区别。
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,
);
}
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,
);
}
复制代码
看了上面的挂载、更新两个阶段的函数对比,咱们很容易发现,更新阶段仅仅比挂载阶段多了一段判断依赖数组是否相同的代码,多作了一个减小重复执行的优化(hook
第一个参数create
)。全部在咱们平时开发中必定要注意useEffect
第二个参数的使用,固然源码中的比较函数,也只是作了一层浅比较。
那么接下来咱们就打开pushEffect
函数看看状况。
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
// 环形链表
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
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;
}
复制代码
其实看完很简单,并无复杂的合并,计算。只是将生成一个effect
对象,并以环形链表的结构存在hook
的memoizedState
属性上,并将此链表赋值给全局变量componentUpdateQueue
。
最中造成一个effect
的环形链表后,放在了componentUpdateQueue
中。至于什么时候触发,找了一会,在commitHookEffectListMount
函数中找到了对hook-effect
的处理
下面咱们就详细的看下
commitHookEffectListMount
如何处理effect
链表
function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
// 作了判断,过滤了依赖重复时,push进来的effect
if ((effect.tag & tag) === tag) {
// Mount
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
复制代码
其实很简单,就是遍历effect链表,并利用tag过滤依赖dept
没变产生的effect
。执行create
函数,而后将create
函数执行的结果赋值给destory
属性。
而后继续在突变阶段-commitWork
函数找到了destory
执行
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & tag) === tag) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
复制代码
useState
,仍是useEffect
都是产生一个链表挂载在当前hook
对象的memoizedState
属性上。useState
产生的是update
更新对象的链表,useEffect
产生的是effect
反作用链表useState
在下次更新时,合并计算hook
对象上的update
链表,最中计算出最新的newState
,赋值给hook.momoizedState
;而useEffect
则在commit
阶段的突变后才开始执行hook
上的effect
链表的create函数,在commit
阶段的突变时执行destory函数