react hook
# 01. hooks demo
let workInProgressHook;
let isMount = true;
const fiber = {
memoizedState: null,
stateNode: App,
};
function run() {
workInProgressHook = fiber.memoizedState;
const app = fiber.stateNode();
isMount = false;
return app;
}
function dispatchAction(queue, action) {
const update = {
action,
next: null,
};
console.log(queue, "update1");
if (queue.pending === null) {
update.next = update;
console.log(update, "update2");
} else {
// 3 -> 0 -> 1 -> 2 -> 3
// 4 -> 0 -> 1 -> 2 -> 3 -> 4
// queue.pending 指向最后一个update,queue.pending.next 指向第一个update
update.next = queue.pending.next; // 4 -> 0
queue.pending.next = update; // 3 -> 4
}
queue.pending = update; // 当前最后一个指针指向update
run();
}
function useState(initialState) {
let hook;
if (isMount) {
hook = {
queue: {
pending: null,
},
memoizedState: initialState,
next: null,
};
if (!fiber.memoizedState) {
fiber.memoizedState = hook;
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
let baseState = hook.memoizedState;
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending);
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function App() {
const [num, updateNum] = useState(0);
console.log(`${isMount ? "mount" : "update"} num: `, num);
return {
click() {
updateNum((num) => num + 1);
},
};
}
window.app = run();
# 02. hooks区分
mount/update下的hooks来源于不同的对象,这类对象称之为: dispatcher
mount时:
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
unstable_isNewReconciler: enableNewReconciler,
};
update时:
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
unstable_isNewReconciler: enableNewReconciler,
};
在mount/update调用的hook是不同的函数。 将不同情况对应的dispatcher赋值给全局变量ReactCurrentDispatcher的current属性。
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
# 03. hook的数据结构
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
}
注意
hook.memoizedState: Hooks链表中保存的单一hook对应的数据
fiber.memoizedState: FunctionComponent 对应fiber保存的Hooks链表
# 04. hooks的调用顺序
当初始化useState的时候,hooks是通过next来绑定state的顺序的,如果在多次调用hooks时,将其中一个useState有条件的省略,不执行,那么.next的时候,获得的state就不是对应的state,会造成state错位。
# 05. memoizedState
不同类型保存的memoizedState数据格式不一致
- useState: 对于const [value, setValue] = useState(initialState),memoizedState保存value的值
- useReducer: 对于const [state, dispatch]= useReducer(reducer, {}),memoized 保存state的值。
- useEffect:memoizedState保存useEffect 回调函数,依赖项等的链表结构effect,effect链表同时会保存在fiber.updateQueue中。
- useMemo: 对于useMemo(callback, [depA]), memoizedState保存(callback, [depA])
- useCallback: 对于useCallback(callback, [depA]),memoizedState保存(callback, [depA]),与useMemo不同的是,useCallback保存的是callback函数本身,useMemo保存的是callback返回的结果
- useRef: useRef(1), memoizedState保存{current: 1}
没有memoizedState:
- useContext
# 06. useState/useReducer
useReducer是useState的替代方案,会接收一个 (state, action) => newState的reducer,并返回当前的state以及配套的dispatch hook的工作流程主要分为:声明阶段和调用阶段。
- 声明阶段就是在所在函数调用时,依次执行useReducer 和useState方法
- 调用阶段即点即按钮后,dispatch或setValue被调用时。
# 07. 声明阶段
render阶段beginwork -> renderWithHooks 方法
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatch.useState(initialState);
}
function useReducer(reducer, initialArg, init) {
var dispater = resolveDispatcher();
var dispatcher.useReducer(reducer, initialArg, init);
}
dispater 在·不同场景下同一个hook会调用不同的函数
# 08. mountState/mountReducer
function mountState<S>(
initialState:(()=>S) | S,
):[S, Dispatch<BasicStateAction<S>>] {
// 创建并返回当前hook
const hook = mountWorkInProgress();
// 赋值初始state
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
// 创建queue
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any)
})
// 创建dispatch
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
queue的数据结构如下:
const queue = (hook.queue = {
pending: null, // 保存update 对象
dispatch: null, // 保存dispatchAction.bind()的值
lastRenderedReducer: reducer, // 上一次render时使用的ruducer
lastRenderedState: (initialState: any)
})
useReducer的lastRenderedReducer为传入的reducer参数。 useState的lastRenderedReducer为basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S
return typeof action === 'function' ? action(state) : action;
useState的reducer参数为basicStateReducer的useReducer
# 两种更新方式
根据action 类型来区分是直接更新还是函数式更新。
// 直接更新
// 适用于与返回的新值与旧值不存在依赖关系
useCount(newState)
//函数式更新。
// preState总能拿到上一次更新成功之后的最新状态
// 如果正常使用,这两种方式没啥区别,但是如果是异步更新的话,他们之间的差别就会体现出来了.
// 这是因为使用函数更新的时候,useCount函数将会被放在一个任务队列中,每一个useCount函数都可以拿到上一次更新成功后状态值。
useCount((preState)=>{return nextState})
# useState 与 setState 区别
- 参数不同
setState(updater,[,callback])
updater:(object/function) -用于更新数据。
callback:function-用于获取更新后的state的值。
useState(initState)
const [state,setState] =useState(initState)
state 代表状态,
setState()
initState是初始状态值。
- setState会自动浅合并,而useState不会。
this.setState({ age:10; }) => this.setState({ ...this.state })
# updateReducer
在update时,useState与useReducer调用的是同一个函数updateReducer
function updateReducer<S, I, A> (
reducer: (S, A) => S,
initialArg: I,
init?: I => S
): [S, Dispatch<A>]{
// 获取当前hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
// 同update与updateQueue 类似的更新逻辑
if (pendingQueue !== null) {
if (baseQueue !== null) {
// 合并待处理队列和基本队列。
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// ...
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
流程概括为: 找到对应的hook,根据update计算该hook的state 并返回
mount与update 获取当前hook方式不一样,mountWorkInProgressHook/updateWorkInProgressHook
- mount是调用ReactDOm.render或初始化API产生的更新,只会执行一次
- update触发更新行为有多种,可能是在事件回调或者副作用触发,又或者是render阶段触发的更新,为了避免组件无限循环更新,需要区别对待
render 假设调用setValue触发更新,不做限制,这次更行会开启一次新的render阶段,导致无限循环。 所以react 用标记变量 didScheduleRenderPhaseUpdate 判断是否是render阶段触发的更新。
# 调用阶段
调用阶段会执行dispatchAction, 整个过程:创建update,将update加入queue.pending中国,并开启调度
function dispatchAction(fiber, queue, action) {
// ... 创建update
var update = {
eventTime: eventTime,
lane: lane,
suspenseConfig: suspenseConfig,
action: action,
eagerReducer: null,
eagerState: null,
next: null
}
// ... 将update加入queue.pending
var alternate = fiber.alternate;
if(fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
// render阶段触发的更新
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
if (fiber.lanes === NOLanes && (alternate === null || alternate.lanes === NoLanes)) {
// ...fiber的updateQueue为空, 优化路径
}
sscheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
if(fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
currentlyRenderingFiber 即 workInProgress, workInProgress 代表当前处于render阶段 触发更新时通过bind预先保存的fiber与workInProgress全等,代表本次更新发生于FunctionComponent 对应fiber 的render阶段。
if (fiber.lanes === NOLanes && (alternate === null || alternate.lanes === NoLanes)) {
fiber.lanes 保存fiber上存在的update优先级 fiber.lanes === NOLanes 代表不存在update
useState整体流程:
# 09. useEffect
在React中类似像componentDidMount这样的生命周期方法中,因为可能会执行setState这样的方法而产生新的更新,我们称之为side effect即副作用。
本身FunctionComponent因为是pure function,所以不会产生任何的副作用,而useEffect和useLayoutEffect是带给FunctionComponent产生副作用能力的Hooks,他们的行为非常类似componentDidMount和componentDidUpdate
他们接受一个方法作为参数,该方法会在每次渲染完成之后被调用;其次还接受第二个参数,是一个数组,这个数组里的每一个内容都会被用来进行渲染前后的对比,如果没有变化,则不会调用该副作用。
# useEffect 执行顺序
React 中 effect hook 的定义
useEffect(didUpdate);
useEffect(didUpdate, dependencies);
用途:effect hook 用于完成副作用操作
参数:
- didUpdate 参数接收一个包含命令式、且可能有副作用代码的函数。
- dependencies 参数接收一个数组,数组中的元素表示 effect 所依赖的值。
执行时机:
- 默认情况:didUpdate 会在每轮组件渲染完成后执行。
- 条件执行:当传入 dependencies 参数时,didUpdate 仅在依赖值发生变化时执行
清除:
- didUpdate 函数可以返回一个清除函数以清除副作用操作(如取消订阅、清除定时器等)。如果组件多次渲染,则上一个 effect 会在下一个 effect 执行之前被清除。
不同组件之间执行顺讯如下:
- 组件渲染后,执行effect的顺序,组件树的
后序
深度优先遍历。 - 组件重新渲染时,清除 effect 的顺序:组件树的
后序
深度优先遍历。 - 组件unmount时,清除effect的顺序,组件树的
前序
深度优选遍历。
# flushPassiveEffectsImpl
flushPassiveEffects: 触发useEffect回调与其他同步任务。内部会设置优先级,并执行flushPassiveEffectsImpl
flushPassiveEffectImpl主要做三件事
- 调用该useEffect在上一次render时的销毁函数
- 调用该useEffect在本次render时的回调函数
- 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行
# 阶段一
useEffect的执行需要保证所有组件useEffect的销毁函数必须都执行完称才能执行任意一个组件的useEffect的回调函数。(因为多个组件可能共用一个ref)
如果不是按照 全部销毁 再 全部执行 的顺序,那么在某个组件useEffect的销毁函数中修改的ref.current可能影响另外一个组件useEffect的回调函数中的同一个ref的current属性。
在阶段一会遍历并执行所有useEffect的销毁函数,pendingPassiveHookEffectsUnmount中保存了所有需要执行销毁的useEffect
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
// pendingPassiveHookEffectsUnmount数组索引【i】保存需要销毁的effect,i+1保存该effect对应的fiber
for (let i = 0; i < unmountEffects; i +=2) {
const effect = ((unmountEffects[i]: any) : HookEffect);
const fiber = ((unmountEffects[i+1]: any): Fiber);
const destory = effect.destory;
effect.destory = undefined;
if (typeof destory === 'function') {
// 销毁函数存在则执行
try {
destory();
} catch {
captureCommitPhaseError(fiber, error);
}
}
}
pendingPassiveHookEffectsUnmount push effect在layout阶段commitLayoutEffectOnFiber内的 schedulePassiveEffects方法
function schedulePassiveEffects(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 {
const {next, tag} = effect;
if (
(tag & HookPassive) !== NoHookEffect &&
(tag & HookHasEffect) !== NoHookEffect
) {
// 向`pendingPassiveHookEffectsUnmount`数组内`push`要销毁的effect
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
// 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effect
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
# 阶段二
与阶段一类似,同样时遍历数组,执行对应effect的回调函数,enqueuePendingPassiveHookEffectMount 同样也在scheduPassiveEffects中, enqueuePassiveHookEffectsMount 保存了所有需要执行回调的useEffect
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = ((mountEffects[i]: any): HookEffect);
const fiber = ((mountEffects[i + 1]: any): Fiber);
try {
const create = effect.create;
effect.destroy = create();
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
# 10. useRef
作用:
- 作用于Dom元素
- 获取子组件的实例(只有类组件可用)
- 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx
在mount/ update 对应两个不同的dispatcher
function mountRef<T>(initialValue: T): {| current: T|}{
// 获取当前useRef hook
cosnt hook = mountWorkInProgress();
// 创建ref
const ref = { current: initialValue};
hook.memoizedState = ref;
return ref;
}
function updateRef<T>(initialValue: T): {|current: T|} {
// 获取当前useRef hook
const hook = updateWorkInProgress();
// 返回保存的数据
return hook.memoizedState;
}
createRef实现:
function createRef(): RefObject {
const refObject = {
current: null
};
return refObject;
}
functionComponent hooks 生命周期:
# useRef 与createRef 区别
- createRef 仅能用在classComponent,useRef仅能用在FunctionComponent
- createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
createRef的值会随着
FunctionComponent
重复执行而不断被初始化 错误示例:永远也拿不到 ref
function App() {
const valueRef = React.createRef(); //随着App render 重复初始化
return <div ref={valueRef} />;
}
const Text = () => {
const [renderIndex, setRenderIndex] = useState(0);
const refFromUseRef = useRef();
const refFromCreateRef = createRef();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex;
}
return <div>
<p>renderIndex: {renderIndex}</p>
<p>
<b>
refFromUseRef: {refFromUseRef.current}
</b>
<b>
refFromCreateRef: {refFromCreateRef.current}
</b>
</p>
<p>
<button onClick={() => setRenderIndex(prevIndex => prevIndex+1)}>点击</button>
</p>
</div>;
};
# useRef与全局变量的区别
在组件外部声明和使用'let'变量不会触发re-rendering组件,与使用useRef相同,后者也不会触发re-rendering,那二者有什么不同?
- useRef 是定义在实例基础上的,如果代码中有多个相同的组件,每个组件的 ref 只跟组件本身有关,跟其他组件的 ref 没有关系。
- 组件前定义的 global 变量,是属于全局的。如果代码中有多个相同的组件,那这个 global 变量在全局是同一个,他们会互相影响。
# ref的工作流程
HostComponent,classComponent,forwardRef 赋值ref属性
- HostComponent
<div ref={domRef}></div>
- classComponent/forwardRef
<App ref={cnRef}/>
其中forwardRef 将ref作为第二参数进行传递,不会进入ref的工作流程。
// secondArg 为传递下去的ref
let children = Component(props, secondArg)
ref的更新在mutation阶段。 mutation执行DOM的依据是effectTag, 所以Ref更新也会赋值对应的effectTag
ref的工作流程分为两步
- render阶段为含有ref属性的fiber 添加Ref effectTag
- commit 阶段为包含Ref effectTag的fiber执行对应的更新
# 阶段一
在render阶段 beginWork与completeWork中 markRef方法为含有ref属性的fiber增加Ref effectTag
// beginWork的markRef
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
// Schedule a Ref effect
workInProgress.effectTag |= Ref;
}
}
// completeWork的markRef
function markRef(workInProgress: Fiber) {
workInProgress.effectTag |= Ref;
}
组件对应fiber被赋值Ref effectTag需要满足的条件:
- mount时,workInProgress.ref !== null,即存在ref属性
- update时,current.ref !== ref ref属性改变
# 阶段二
- 针对属性改变的,需要先移除之前的ref
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
// 移除之前的ref
commitDetachRef(current);
}
}
}
}
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef!== null) {
if (typeof currentRef === 'function') {
currentRef(null);
} else {
currentRef.current = null;
}
}
}
- ref 赋值(commitLayoutEffect 执行 commitAttachRef)
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
// 获取ref属性对应的Component实例
const instance = finishedWork.stateNode;
let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
// 赋值ref
if (typeof ref === 'function') {
ref(instanceToUse);
} else {
ref.current = instanceToUse;
}
}
}
# 11. useCallback/useMemo
const memoizedCallback = useCallback(() => {
do(depA, depB)
}, [depA, depB])
// 返回一个 memoized 回调函数 。
const memoizedValue = useMemo(() => {
computeValue(depA, depB)
}, [depA, depV])
// 返回一个 memoized 值 。
useCallback(fn, deps) 类似于 useMemeo(()=> fn,deps)
# mount
function mountMemo<T> (
nextCreate: () => T,
deps: Array<mixed> | void | null
) : T {
// 创建并返回当前hook
const hook = mountWorkInProgress();
const nextDeps = deps === undefined ? null : deps;
// 计算value
const nextValue = nextCreate();
// 将value与deps保存在hook.memeoized
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null) : T {
// 创建并返回hook
const hook = mountWorkInProgress();
const nextDeps = deps === undefined ? null : deps;
// 将value与deps保存在hook.memoizedState中
hook.memoizedState = [callback,deps];
return callback
}
# update
function updateMemo<T> (
nextCreate: () => T,
deps: Array<mixed> | void | null
){
// 返回当前的hook
const hook = updateWorkInProgress();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 判断update前后value是否变化
if(areHookInputsEquql(nextDeps. prevDeps)) {
// 未变化
return prevState[0];
}
}
}
// 变化。 需要重新计算
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 返回当前hook
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 判断update前后value是否变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 未变化
return prevState[0];
}
}
}
// 变化,将新的callback作为value
hook.memoizedState = [callback, nextDeps];
return callback;
}
二者区别value是回调函数本身还是回调函数的执行结果。