React 暴露出来的部分 Hooks
//packages/react/src/React.jsexport { ... useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, ...}
复制代码
功能描述
useState、useReducer: 状态值相关
useEffect、useLayoutEffect: 生命周期相关
useContext: 状态共享相关
useCallback、useMemo: 性能优化相关
useRef: 属性相关
源码
export function useContext<T>( Context: ReactContext<T>, unstable_observedBits: number | boolean | void,): T { const dispatcher = resolveDispatcher(); ... return dispatcher.useContext(Context, unstable_observedBits);}
export function useState<S>( initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState);}
export function useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S,): [S, Dispatch<A>] { const dispatcher = resolveDispatcher(); return dispatcher.useReducer(reducer, initialArg, init);}
export function useRef<T>(initialValue: T): {|current: T|} { const dispatcher = resolveDispatcher(); return dispatcher.useRef(initialValue);}
export function useEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null,): void { const dispatcher = resolveDispatcher(); return dispatcher.useEffect(create, deps);}
export function useLayoutEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null,): void { const dispatcher = resolveDispatcher(); return dispatcher.useLayoutEffect(create, deps);}
export function useCallback<T>( callback: T, deps: Array<mixed> | void | null,): T { const dispatcher = resolveDispatcher(); return dispatcher.useCallback(callback, deps);}
export function useMemo<T>( create: () => T, deps: Array<mixed> | void | null,): T { const dispatcher = resolveDispatcher(); return dispatcher.useMemo(create, deps);}
// resolveDispatcherfunction resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; invariant( ... ); return dispatcher;}
// ReactCurrentDispatcherconst ReactCurrentDispatcher = { /** * @internal * @type {ReactComponent} */ current: (null: null | Dispatcher),};
复制代码
其实hooks的定义都来自dispatcher,那我们根据Dispatcher依次去看看他们的实际实现。
Dispatcher
export type Dispatcher = {| ... useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>], useReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: (I) => S, ): [S, Dispatch<A>], useContext<T>( context: ReactContext<T>, observedBits: void | number | boolean, ): T, useRef<T>(initialValue: T): {|current: T|}, useEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void, useLayoutEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void, useCallback<T>(callback: T, deps: Array<mixed> | void | null): T, useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T, ...|};
复制代码
很明显这就是各种hooks的定义,但是总归都要是参加到执行的的流程里面去的,函数组件也属于ReactComponent的一种,他也有mount和update阶段。
函数组件 Mount 阶段
我们在前面提到执行beginWork函数中,我们发现会有tag为FunctionComponent的选项,他会调用updateFunctionComponent进而调用renderWithHooks进行更新。
// packages/react-reconciler/src/ReactFiberBeginWork.old.jsfunction beginWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,): Fiber | null { const updateLanes = workInProgress.lanes; switch (workInProgress.tag) { case FunctionComponent: { const Component = workInProgress.type; // 组件类型 const unresolvedProps = workInProgress.pendingProps; // 未处理的props ... return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } case ClassComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); // 渲染classComponent return updateClassComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } case HostRoot: ... case HostComponent: ... case HostText: ... }}
复制代码
renderWithHooks
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; // fiber树 currentlyRenderingFiber = workInProgress;
... // 重置memoizedState、updateQueue、lanes workInProgress.memoizedState = null; workInProgress.updateQueue = null; workInProgress.lanes = NoLanes; ...
if (__DEV__) { ... } else { // 判断mount update阶段 ReactCurrentDispatcher.current = current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; }}
复制代码
HooksDispatcherOnMount
const HooksDispatcherOnMount: Dispatcher = { useCallback: mountCallback, useContext: readContext, useEffect: mountEffect, useLayoutEffect: mountLayoutEffect, useMemo: mountMemo, useReducer: mountReducer, useRef: mountRef, useState: mountState, ...};
复制代码
在mount的情况下,每个hook是都有自己对应的mountXxx,useState的对应的就是mountState,不过在讲mountState之前我们要去看一个东西 -- type hook,因为实际的开发当中不可能只用一个 hook,多个 hook 他就会形成链表,保存在 fiber 的 memoizedState 上面。相关参考视频讲解:进入学习
type Hook
export type Hook = {| memoizedState: any, // 单独的hook唯一值 baseState: any, // 初始state baseQueue: Update<any, any> | null, // 初始更新队列 queue: UpdateQueue<any, any> | null, // 更新队列 next: Hook | null, // hooks链表下一个指针|};
复制代码
那么我们去看看他是如何关联起来的:const hook = mountWorkInProgressHook()
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 // 第一个hook currentlyRenderingFiber.memoizedState = workInProgressHook = hook; } else { // Append to the end of the list // 往后面添加,形成链表 workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook;}
复制代码
mountState
function mountState<S>( initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
// 创建并关联hook链表 const hook = mountWorkInProgressHook(); // 如果是函数入参,则是把函数的执行结果当做入参 if (typeof initialState === 'function') { // $FlowFixMe: Flow doesn't like mixed types initialState = initialState(); } // 把hook的memoizedState变为初始值 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)); // 返回 memoizedState, dispatch return [hook.memoizedState, dispatch];}
复制代码
根据mountState我们就知道在写useState的时候,解构的语法为什么会解构出想要的东西了。
const [mutation, setMutation] = useState(0) // 纯数字作为初始值
const [mutation, setMutation] = useState(()=>handle(1)) // 函数作为初始值,函数的返回值作为初始值
这里我们遗留了一个问题
针对于上述问题,我们必须去看一下dispatch到底干了什么事情。
const dispatch: Dispatch< BasicStateAction<S>, > = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue, ): any));
复制代码
dispatchAction
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A,) { if (__DEV__) { ... } // 获取触发更新的时间 const eventTime = requestEventTime(); // 获取更新优先级 const lane = requestUpdateLane(fiber);
// 创建更新 const update: Update<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null: any), };
// 维护链表,在update阶段,如果有更新任务则一直延续 const pending = queue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } // 加入更新队列 queue.pending = update;
const alternate = fiber.alternate;
// 如果是render阶段更新 if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) ) { ... didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else { if ( fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes) ) {
// 当前没有更新,是第一次调用dispatchAction,计算state const lastRenderedReducer = queue.lastRenderedReducer; if (lastRenderedReducer !== null) { let prevDispatcher; ... try { const currentState: S = (queue.lastRenderedState: any); // render阶段,如果reducer没有变化,直接取值eagerState const eagerState = lastRenderedReducer(currentState, action);
// render阶段,如果reducer没有变化,直接取值eagerState update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { // 如果当前的值遇前面的值相同直接返回上一个值,不发起更新 return; } } catch (error) { ... } finally { ... } } } ...
//更新 scheduleUpdateOnFiber(fiber, lane, eventTime); }}
复制代码
这里我们可以看到usetate的回调,就是创建更新,维护了一个链表结构,在render阶段我们根据eagerState与这次的currentState比较,决定要不要发起更新,执行scheduleUpdateOnFiber进行更新
函数组件 Update 阶段
const HooksDispatcherOnUpdate: Dispatcher = { useCallback: updateCallback, useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, useRef: updateRef, useState: updateState, ...};
复制代码
update阶段中的useState调用了updateState,而在updateState中再次调用updateReducer
function updateState<S>( initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] { // return updateReducer(basicStateReducer, (initialState: any));}
复制代码
在basicStateReducer中进行了如下处理:
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { // $FlowFixMe: Flow doesn't like mixed types // 如果更新为函数则是函数执行返回值为更新值 return typeof action === 'function' ? action(state) : action;}
复制代码
这里也就是说明了我们在写hook的时候:
setMutation(mutation + 1) // 直接赋值
setMutation(()=>handle(1)) // 函数值返回值作为新值赋值
useReducer
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; invariant( queue !== null, 'Should have a queue. This is likely a bug in React. Please file an issue.', );
// 更新 queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
// The last rebase update that is NOT part of the base state. let baseQueue = current.baseQueue;
// 如果最后一个更新还没有完毕,则继续更新 const pendingQueue = queue.pending; if (pendingQueue !== null) {
// 如果在更新的过程中有新的更新,加入更新队列 if (baseQueue !== null) { const baseFirst = baseQueue.next; const pendingFirst = pendingQueue.next; baseQueue.next = pendingFirst; pendingQueue.next = baseFirst; } if (__DEV__) { if (current.baseQueue !== baseQueue) { // Internal invariant that should never happen, but feasibly could in // the future if we implement resuming, or some form of that. console.error( 'Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.', ); } } current.baseQueue = baseQueue = pendingQueue; queue.pending = null; }
if (baseQueue !== null) { // We have a queue to process. const first = baseQueue.next; let newState = current.baseState;
let newBaseState = null; let newBaseQueueFirst = null; let newBaseQueueLast = null; let update = first;
do { //获取每一个任务的更新优先级 const updateLane = update.lane;
// 如果当前的执行任务优先级不高,则跳过当前任务,去执行优先级高的任务 if (!isSubsetOfLanes(renderLanes, updateLane)) { // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state.
// 克隆更新任务 const clone: Update<S, A> = { lane: updateLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; newBaseState = newState; } else { newBaseQueueLast = newBaseQueueLast.next = clone; }
// 如果都是低级任务,则合并优先级 currentlyRenderingFiber.lanes = mergeLanes( currentlyRenderingFiber.lanes, updateLane, ); markSkippedUpdateLanes(updateLane); } else { // This update does have sufficient priority.
if (newBaseQueueLast !== null) { // 如果更新队列还有更新任务 const clone: Update<S, A> = { // This update is going to be committed so we never want uncommit // it. Using NoLane works because 0 is a subset of all bitmasks, so // this will never be skipped by the check above. lane: NoLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; // 把更新任务插入到队列末尾 newBaseQueueLast = newBaseQueueLast.next = clone; }
// 执行每一个reducer获取newState // 如果每一次的reducer相同,则把eagerState赋给newState if (update.eagerReducer === reducer) { // If this update was processed eagerly, and its reducer matches the // current reducer, we can use the eagerly computed state. newState = ((update.eagerState: any): S); } else { // 否则就需要去发起更新 const action = update.action; newState = reducer(newState, action); } } // 继续更新 update = update.next; } while (update !== null && update !== first);
if (newBaseQueueLast === null) { newBaseState = newState; } else { newBaseQueueLast.next = (newBaseQueueFirst: any); }
// Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); }
// 赋值返回最新状态 hook.memoizedState = newState; hook.baseState = newBaseState; hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState; }
const dispatch: Dispatch<A> = (queue.dispatch: any); return [hook.memoizedState, dispatch];}
复制代码
所以,useState大致的执行流程如下:
既然useReducer与useState同为状态钩子,那就来看一看userReducer的实现吧
useReducer 的用法
import React, {useReducer} from 'react';const initialState = {num: 0};const reducer = (state, action) => { switch(action.type){ case 'ad': return {num: state.num + 1}; case 'de': return {num: state.num - 1}; case 'ch': return {num: state.num * 2}; case 'dv': return {num: state.num / 2}; default: return state }}
const App = () => { const [state, dispatch] = useReducer(reducer, initialState); retrun ( <div className="App"> <h1>{state.num}</h1> <div onClick={() => dispatch({type:ad})}>ad</div> <div onClick={() => dispatch({type:de})}>de</div> <div onClick={() => dispatch({type:ch})}>ch</div> <div onClick={() => dispatch({type:dv})}>dv</div> </div> )}
export default App
复制代码
各位可以自行去 codeSandBox 上面去测试玩耍。
分析上述代码我们可以看到每一个div的点击会绑定一个dispatch,这个dispatch可以实现useState里面的setMutation的功能,维护的state值来自于initialState,action为dispatch的入参,针对于这一些,我们去看看源码。
useReducer 的 mount 阶段
我们根据HooksDispatcherOnMount可以找到在mount阶段,useReducer调用的是mountReducer
mountReducer
function mountReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S,): [S, Dispatch<A>] { //关联链表 const hook = mountWorkInProgressHook(); let initialState; if (init !== undefined) { initialState = init(initialArg); } else { initialState = ((initialArg: any): S); } // 保存初始值 hook.memoizedState = hook.baseState = initialState;
//更新队列 const queue = (hook.queue = { pending: null, dispatch: null, lastRenderedReducer: reducer, lastRenderedState: (initialState: any), }); // 处理回调 const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue, ): any)); //返回hook.memoizedState与回调 return [hook.memoizedState, dispatch];}
复制代码
其实这个函数与useState非常类似,只不过他的第一入参为reducer,并且附带了一个可选参数init,官网上面对其称之为惰性初始化。update阶段调用的是updateReducer,在这里不再复述。
生命周期相关的 hook
生命周期相关的这里讲解两个useEffect、useLayoutEffect,面试中经常会问到这两个有什么区别,其实他们并没有什么区别,只是执行的时机不同,具体表现为:
操作 dom 性能相关问题为什么修改 dom 建议放在 useLayoutEffect 中而不是 useEffect 中呢?
mountEffect
function mountEffect( // 传入的函数useEffect(()=>{},[]) create: () => (() => void) | void, // 依赖 deps: Array<mixed> | void | null,): void { if (__DEV__) { ... } } return mountEffectImpl( UpdateEffect | PassiveEffect, HookPassive, create, deps, );}
复制代码
mountEffect调用mountEffectImpl
mountEffectImpl
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, );}
复制代码
pushEffect
function pushEffect(tag, create, destroy, deps) { // 入参为副作用标记,传入函数,销毁回调,依赖项 const effect: Effect = { tag, create, destroy, deps, // Circular // 下一个副作用 next: (null: any), };
//获取函数组件更新队列 let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) { //队列为空,创建队列 componentUpdateQueue = createFunctionComponentUpdateQueue(); //与fiber更新队列关联起来 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;}
复制代码
这里做的事情,无非就是关联componentUpdateQueue与effect,让React去调度执行。
updateEffect
updateEffect执行调用了updateEffectImpl:
// updateEffectfunction updateEffect( // 传入函数 create: () => (() => void) | void, // 依赖项 deps: Array<mixed> | void | null,): void { if (__DEV__) { ... } return updateEffectImpl( UpdateEffect | PassiveEffect, HookPassive, create, deps, );}
//updateEffectImplfunction updateEffectImpl(fiberFlags, hookFlags, create, deps): void { const hook = updateWorkInProgressHook();// 获取更新hook 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)) { // 即使依赖相同,也要讲effect加入链表,保证顺序 pushEffect(hookFlags, create, destroy, nextDeps); return; } } }
currentlyRenderingFiber.flags |= fiberFlags;
//在commit阶段处理effect hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, destroy, nextDeps, );}
复制代码
本着学习到底的精神,我们一起来看看比较两次的依赖,到底是怎么比较的:
areHookInputsEqual
function areHookInputsEqual( nextDeps: Array<mixed>, //当前的依赖 prevDeps: Array<mixed> | null,// 上一次的依赖) { if (__DEV__) { ... }
// 上一次为空,肯定不一样,则必须更新 if (prevDeps === null) { if (__DEV__) { ... } return false; }
if (__DEV__) { if (nextDeps.length !== prevDeps.length) { ... } // 遍历两个依赖数组,根据下标比较 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true;}
// import is form 'shared/objectIs'const objectIs: (x: any, y: any) => boolean = // 兼容Object.is typeof Object.is === 'function' ? Object.is : is;
// is function is(x: any, y: any) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) );}
复制代码
上述代码介绍了比较两种值是否一样,其中 Object.is 的浏览器适用版本比较高。做了一次兼容:
上面说了useEffect与useLayoutEffect的结构都是一样的,重点只是在于他们俩的执行时机不一样,那么我们就去深究一下useEffect与useLayoutEffect的执行时机。
生命周期相关的 hook 的执行时机
我们知道所谓生命周期钩子,那肯定是在某一阶段去执行的,这个阶段就是commit阶段。在commit阶段的commitLayoutEffects函数中执行一系列的生命周期钩子,但是对于函数组件来讲,会调度useEffect的create和destroy,也就是执行schedulePassiveEffects函数。
function schedulePassiveEffects(finishedWork: Fiber) { // 获取更新队列 const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); // 获取最后一个effect 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 ) { // 将effect中的destroy和create加入到e //pendingPassiveHookEffectsUnmount //pendingPassiveHookEffectsMount中 enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect !== firstEffect); }}
// enqueuePendingPassiveHookEffectMountexport function enqueuePendingPassiveHookEffectMount( fiber: Fiber, effect: HookEffect,): void { pendingPassiveHookEffectsMount.push(effect, fiber); if (!rootDoesHavePassiveEffects) { // root上有没有副作用,默认值为false rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); }}
// enqueuePendingPassiveHookEffectUnmountexport function enqueuePendingPassiveHookEffectUnmount( fiber: Fiber, effect: HookEffect,): void { pendingPassiveHookEffectsUnmount.push(effect, fiber); if (__DEV__) { fiber.flags |= PassiveUnmountPendingDev; const alternate = fiber.alternate; if (alternate !== null) { alternate.flags |= PassiveUnmountPendingDev; } } if (!rootDoesHavePassiveEffects) { // root上有没有副作用,默认值为false rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); }}
复制代码
调度执行 flushPassiveEffects
调度执行flushPassiveEffect会把当前正在执行的任务优先级跟后来新增的优先级进行比较。
export function flushPassiveEffects(): boolean { ... if (decoupleUpdatePriorityFromScheduler) { // 获得更新优先级 const previousLanePriority = getCurrentUpdateLanePriority(); try { ... return runWithPriority(priorityLevel, flushPassiveEffectsImpl); } finally { ... } } else { return runWithPriority(priorityLevel, flushPassiveEffectsImpl); } } return false;}
复制代码
flushPassiveEffectsImpl
function flushPassiveEffectsImpl() {
// First pass: Destroy stale passive effects. const unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = []; for (let i = 0; i < unmountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); const fiber = ((unmountEffects[i + 1]: any): Fiber); const destroy = effect.destroy; effect.destroy = undefined;
... if (typeof destroy === 'function') { ... } else { try { if ( ... ) { try { startPassiveEffectTimer(); destroy();//销毁回调执行 } finally { recordPassiveEffectDuration(fiber); } } else { destroy();//销毁回调执行 } } catch (error) { ... } } } } // Second pass: Create new passive effects. 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); if (__DEV__) { ... } else { try { const create = effect.create; if ( ... ) { try { startPassiveEffectTimer(); effect.destroy = create();//本次的render的创建函数 } finally { recordPassiveEffectDuration(fiber); } } else { effect.destroy = create();//本次的render的创建函数 } } catch (error) { ... } } } ... return true;}
复制代码
在这个函数里面就是依次执行上一次的render销毁回调函数、本次的render创建回调函数。而在前面也说过commit流程是无法中断的,只有等所有节点全部commit完,浏览器才会去告知react可以执行自己的调度任务了,也正在此刻useEffect所对应的函数才会去执行,
在生命周期hook里面React帮我们做了一定的性能优化,除了这个还提供了几个手动优化的hook,useMemo和useCallback,那我们来一起瞧瞧吧。
性能优化相关 hook
在讲这一部分之前我们应该搞明白一个问题
先回答第一个问题,为什么要用他们来做性能优化: 我们要知道,React更新流程中只要组件中props或者state发生了变化,那就是必须从变化顶部更新所有的后代组件,牵一发而动全身。有些组件我们并不想要它重新渲染,它却做了一定量的性能牺牲,用useMemo、useCallback就可以改变这样的局面。那么应该怎么用他们来做性能优化呢,主要体现在三个方面:
避免无效的副作用
避免无效的累计计算
避免无效的重新渲染
因为我们前面讲到useEffect,我们可以期望在依赖发生变更的时候去做我们想要做的事情,例如接口请求
const App = () => { // 只有当parmas发生变化的时候,我们才回去执行fetch() useEffect(()=>{ fetch() }, [params])}
复制代码
这并不是他们俩的功能呢,没关系,我们可以变着法子让他们俩具有类似功能,不过这就有点牵强了。那怎么去做无效的计算和无效的重复渲染呢?有这样的一道面试题:
// 点击父组件里面的按钮,会不会在子组件里面打印“子组件渲染了”?如果会,该怎么优化?import { useState } from "react";
// 父组件export const Parent = () => { console.log("父组件渲染了"); const [count, setCount] = useState(0); const increment = () => setCount(count + 1);
return ( <div> <button onClick={increment}>点击次数:{count}</button> <Child /> </div> );};
// 子组件export const Child = ({}) => { console.log("子组件渲染了"); return <div>子组件</div>;};
复制代码
很明显点击父组件,改变了count,整个组件都要重新渲染。肯定会打印的,那怎么优化呢?
import { useState, useMemo } from "react";
// 父组件export const Parent = () => { console.log("父组件渲染了"); const [count, setCount] = useState(0); const increment = () => setCount(count + 1);
return ( <div> <button onClick={increment}>点击次数:{count}</button> // 这种写法不太提倡,一般采用的是memo {useMemo(()=>(<Child />),[])} </div> );};
// 子组件export const Child = ({}) => { console.log("子组件渲染了"); return <div>子组件</div>;};
//export const Child = memo(({}) => {// console.log("子组件渲染了");// return <div>子组件</div>;//});
复制代码
那么避免无效的计算体现在哪里呢:
import { useState } from "react";
const App = () => { const [x, setX] = useState(0); const [y, setY] = useState(1);
const caculate = () => { console.log('正在计算中') //庞大的计算,跟x相关的计算 return x }
return ( <div> <h4>caculate:{caculate}</h4> <h4>x:{x}</h4> <h4>y:{y}</h4> <div onClick={()=>setX(x + 1)}>x+1</div> <div onClick={()=>setY(y + 1)}>y+1</div> </div> )}
复制代码
上面的代码里面的函数caculate只是一个与x有关的处理逻辑,与y没有关系,但是我们知道,只要触发了render,那么整个脚本就会自上而下执行一遍,很明显,如果这里只触发setY的话,他也会重新执行一遍脚本,而在caculate里面具有大量的耗时计算,那么这个时候,再次重新触发就显得很没有必要,故而我们可以采用useMemo来进行优化。
import { useState } from "react";
const App = () => { const [x, setX] = useState(0); const [y, setY] = useState(1);
const caculate = useMemo(() => { console.log('正在计算中') //庞大的计算,跟x相关的计算 return x },[x]) // 只有x变化的时候,才会重新执行caculate
return ( <div> <h4>caculate:{caculate}</h4> <h4>x:{x}</h4> <h4>y:{y}</h4> <div onClick={()=>setX(x + 1)}>x+1</div> <div onClick={()=>setY(y + 1)}>y+1</div> </div> )}
复制代码
useCallback适用于父子组件嵌套,父组件基于属性把方法传递给子组件,保证了每一次父组件更新不会重新创建函数堆,而是获取之前的引用,传递给子组件的属性就没有变化,例如:
// 父组件import Child from './Child'const Parent = () => { console.log('父组件渲染了') const [info, setInfo] = useState({name:'一溪之石', age: 18}); const [count, setCount] = useState(1);
const tansfromChild = () => { //.... }
//采用useCallback包裹 //const tansfromChild = useCallback(() => { // //.... //})
const handleSome = () => { setCount(count + 1) } return ( <> <div onClick={() => handleSome}>父组件</div> <Child tansfromChild={tansfromChild} info={info}/> </> )}
// 子组件const Child = ({tansfromChild, info}) => { console.log('子组件渲染了') return ( <div onClick={() => tansfromChild}>{info.name}{info.age}</div> )}
复制代码
上述代码中点击父组件的函数,必定会使得count + 1,从而会重新渲染父组件,重新创建函数(不考虑info),对于子组件来说,props改变,就一定会发生重新渲染。针对这种情况,我们一般采用用useCallback包裹起来,然后你去执行点击父组件的函数,你发现他依旧是会重新渲染子组件。因为子组件是函数组件。他没有处理props的浅比较,故而每一次的props他都是不一样的。针对于这种问题解决方式:
因为PureComponent里面会有一个shouldComponentUpdate来处理props的浅比较,而memo则也是我们现在需要去探索的东西。所以对于 useCallback 的使用一定要配合上具有浅比较的组件使用,否则不能优化性能,反而浪费性能。
useMemo适用于大量的计算得出的结果,防止在组建更新的时候,避免不必要的重新计算。
说了这么多,我们还是一起来看看他们是如何实现的吧,根据上面的经验,我们可以在HooksDispatcherOnMount里面找到mountCallback和mountMemo:
mountCallback、mountMemo
// mountCallbackfunction mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = mountWorkInProgressHook(); // 获取hook链表,并关联 const nextDeps = deps === undefined ? null : deps; // 依赖 hook.memoizedState = [callback, nextDeps]; // 存入memoizedState return callback; // 返回传入的函数}
// mountMemofunction mountMemo<T>( nextCreate: () => T, deps: Array<mixed> | void | null,): T { const hook = mountWorkInProgressHook(); // 获得hook,关联链表 const nextDeps = deps === undefined ? null : deps; // 依赖 const nextValue = nextCreate(); // 执行传入函数,把值给nextValue hook.memoizedState = [nextValue, nextDeps]; // 把值和依赖加入到memoizedState中 return nextValue; // 把传入函数的执行返回值,返回}
复制代码
上述代码简单如斯,mountCallback直接把传入的函数返回出去了,而mountMemo会把传入的函数执行,把返回值返回出去。并没有什么好讲的,我们看看他们怎么更新吧。
updateCallback、updateMemo
// updateCallbackfunction updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = updateWorkInProgressHook(); // 获取更新hook const nextDeps = deps === undefined ? null : deps; // 依赖 // [callback, nextDeps]上一个的hook.memoizedState const prevState = hook.memoizedState;
if (prevState !== null) { // 上一个hook.memoizedState不为空 if (nextDeps !== null) { // 这次也不为空
const prevDeps: Array<mixed> | null = prevState[1]; // [callback, nextDeps]取出依赖赋给prevDeps
if (areHookInputsEqual(nextDeps, prevDeps)) { // 如果依赖一样,则返回上一次的函数(缓存函数) return prevState[0]; } } } // 如果上一次没有,表示首次渲染,则记录函数与依赖项,并返回传入的函数 hook.memoizedState = [callback, nextDeps]; return callback;}
//updateMemofunction updateMemo<T>( nextCreate: () => T, deps: Array<mixed> | void | null,): T { const hook = updateWorkInProgressHook(); // 获取更新hook const nextDeps = deps === undefined ? null : deps; //依赖
const prevState = hook.memoizedState; // 获取上一次的值(缓存值)[nextValue, nextDeps]
if (prevState !== null) { // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; // 比较两次依赖 if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; //如果相等就返回上一次的计算结果值 } } } // 首次渲染,执行会犯传入函数的结果 const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps];// 存入hook.memoizedState return nextValue; // 返回计算结果}
复制代码
两个函数的更新也是异曲同工,细节都写在了代码里面,也没什么好讲的。
useContext
开始看 useContext 之前,我们要明白这个 hook 可以用来干什么。
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
听的云里雾里的?不要担心,说白了就是组件之间传值就完事了,那么你又说了,组件传值我们用props传递不就完毕了吗,为什么还要有这个,太天真。useContext提供的功能是可以跨层级的。假如有ABCDE五个组件嵌套,你用props传值,是不是要A传到B...一直传到E,但是useContext就可以在E组件里面直接获取到A的状态,举个例子:
const ComponentA = () => { const [count, setCount] = useState(0); return ( <div> <h4>父级:{count}</h4> <ComponentB count={count}/> </div> )}
const ComponentB = (count) => { return ( <div> <h4>父级:{count}</h4> <ComponentC count={count} /> </div> )}
const ComponentC = (count) => { return ( <div> <h4>父级:{count}</h4> <ComponentD count={count} /> </div> )}
const ComponentD = (count) => { return ( <div> <h4>父级:{count}</h4> <ComponentE count={count}/> </div> )}
const ComponentE = (count) => { return ( <div> <h4>来自A父级的count:{count}</h4> </div> )}
复制代码
这样才能在 E 组件里面拿到来自 A 组件的 count,但是 useContext 提供了,直接拿的功能,例如:
// 用createContext创建上下文const Content = createContext(null);
// 在父组件里面用Content.Provider包裹子组件,指定上下文范围const ComponentA = () => { const [count, setCount] = useState(0); return ( <div> <h4>父级:{count}</h4> <Content.Provider value={{count,setCount}}> <ComponentB /> </Content.Provider> </div> )}...
const ComponentE = (count) => { // 需要用到的组件里面用useContext接收 const [count, setCount] = useContext(Content) return ( <div> <h4>来自A父级的count:{count}</h4> </div> )}
复制代码
所以useContext的用法无非就三点:
createContext创建上下文
Content.Provider指定上下文
useContext使用上下文
既然知道他的用法,那么一起瞧瞧他的实现吧,首先我们肯定要去关注一下createContext:
createContext
export function createContext<T>( defaultValue: T, // 传入的默认值 calculateChangedBits: ?(a: T, b: T) => number, // 可选参数处理默认值的变化): ReactContext<T> { // 没有传入处理函数 if (calculateChangedBits === undefined) { calculateChangedBits = null; } else { ... }
// context对象 const context: ReactContext<T> = { $$typeof: REACT_CONTEXT_TYPE, _calculateChangedBits: calculateChangedBits, _currentValue: defaultValue, _currentValue2: defaultValue, _threadCount: 0, Provider: (null: any), Consumer: (null: any), };
//context.Provider context.Provider = { $$typeof: REACT_PROVIDER_TYPE, // element类型 _context: context, };
let hasWarnedAboutUsingNestedContextConsumers = false; let hasWarnedAboutUsingConsumerProvider = false; let hasWarnedAboutDisplayNameOnConsumer = false;
if (__DEV__) { ... context.Consumer = Consumer; // dev环境下Consumer就是Consumer } else { context.Consumer = context;// 正常情况下,Consumer用来包裹接收状态的组件的 } ... return context;}
复制代码
上面的代码主要关注于context.Provider和context.Consumer,先来解释一下他们两个的作用吧。
eg:
// ComponentAconst ComponentA = () => { const [count, setCount] = useState(0); return ( <div> <h4>父级:{count}</h4> <Content.Provider value={{count,setCount}}> <ComponentB /> </Content.Provider> </div> )}
// ComponentBconst ComponentB = (count) => { // 需要用到的组件里面用useContext接收 // const [count, setCount] = useContext(Content) return ( <div> <context.Consumer> {(count)=><h4>来自A父级的count:{count}</h4>} </context.Consumer> </div> )}
复制代码
可见不用从useContext中解构出来,而用<context.Consumer>包裹起来,也是可以达到一样的效果的,他们之间的关系大致可以这样表示:
useContext 的执行流程
谈到useContext这里就不得不跟react里面的context一起说一下,在react源码中存在一个valueStack和valueCursor用来记录context的历史信息和当前context,didPerformWorkStackCursor用来表示当前的context有没有变化。
//ReactFiberNewContext.old.jsconst valueCursor: StackCursor<mixed> = createCursor(null);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
复制代码
//ReactFiberStack.old.jsconst valueStack: Array<any> = [];
复制代码
//pushProviderfunction pushProvider(providerFiber, nextValue) { var context = providerFiber.type._context; { push(valueCursor, context._currentValue, providerFiber); context._currentValue = nextValue; }}
//popProviderfunction popProvider(providerFiber) { var currentValue = valueCursor.current; pop(valueCursor, providerFiber); var context = providerFiber.type._context;
{ context._currentValue = currentValue; }}
复制代码
因为在render阶段,会以深度优先的方式遍历节点,这样在每一层级都可以通过valueCursor拿到最新的值了。上面也讲了createContext的实现,那么在使用的时候useContext又在干了什么呢?我们通过调用可以看到是调用了readContext,而readContext创建了 dependencies并关联到了fiber的dependencies上面。更新阶段再次调用readContext去根据context的变化执行调度更新。
export function readContext<T>( context: ReactContext<T>, observedBits: void | number | boolean,): T { ...
if (lastContextWithAllBitsObserved === context) { // Nothing to do. We already observe everything in this context.
// 走mount阶段的useContext逻辑 } else if (observedBits === false || observedBits === 0) { // Do not observe any updates. } else {
// 生成resolvedObservedBits let resolvedObservedBits; // Avoid deopting on observable arguments or heterogeneous types. if ( typeof observedBits !== 'number' || observedBits === MAX_SIGNED_31_BIT_INT ) { // Observe all updates. lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>); resolvedObservedBits = MAX_SIGNED_31_BIT_INT; } else { resolvedObservedBits = observedBits; }
// 生成dependencies const contextItem = { context: ((context: any): ReactContext<mixed>), observedBits: resolvedObservedBits, next: null, };
if (lastContextDependency === null) { invariant( ... );
// This is the first dependency for this component. Create a new list. lastContextDependency = contextItem; // fiber dependencies链表的第一个context currentlyRenderingFiber.dependencies = { lanes: NoLanes, firstContext: contextItem, responders: null, }; } else { // Append a new context item. // 加入dependencies lastContextDependency = lastContextDependency.next = contextItem; } } return isPrimaryRenderer ? context._currentValue : context._currentValue2;}
复制代码
我们再来看一看render阶段 react 会根据不同的组件类型去执行updateContextProvider,updateContextConsumer。
updateContextProvider
function updateContextProvider( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,) { const providerType: ReactProviderType<any> = workInProgress.type; const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps; // 新的状态 const oldProps = workInProgress.memoizedProps;// 上一次的状态
const newValue = newProps.value;// 新状态的value ...
// 把value加入到valueStack中 pushProvider(workInProgress, newValue);
if (oldProps !== null) { const oldValue = oldProps.value;
// 计算context变化的值 const changedBits = calculateChangedBits(context, newValue, oldValue); if (changedBits === 0) { // context没有变化 // No change. Bailout early if children are the same. if ( oldProps.children === newProps.children && !hasLegacyContextChanged() ) { return bailoutOnAlreadyFinishedWork( current, workInProgress, renderLanes, ); } } else { // context有变化了 // The context value changed. Search for matching consumers and schedule // them to update. propagateContextChange(workInProgress, context, changedBits, renderLanes); } }
const newChildren = newProps.children; // context变化,要重新diff reconcileChildren(current, workInProgress, newChildren, renderLanes); return workInProgress.child;}
复制代码
上面代码可以看到如果changedBits === 0则表示当前context没有变化。
calculateChangedBits
export function calculateChangedBits<T>( context: ReactContext<T>, newValue: T, oldValue: T,) { // 两次的props是一样的 if (is(oldValue, newValue)) { // No change return 0; } else { // 两次props不一样 const changedBits = typeof context._calculateChangedBits === 'function' ? context._calculateChangedBits(oldValue, newValue) : MAX_SIGNED_31_BIT_INT;
... return changedBits | 0; // 返回0或者MAX_SIGNED_31_BIT_INT // MAX_SIGNED_31_BIT_INT = 1073741823,31位调度算法 }}
复制代码
计算完 changedBits 之后,如果没有变化就执行bailoutOnAlreadyFinishedWork,跳过当前的节点更新。如果有变化了则执行propagateContextChange去更新当前节点信息。
// bailoutOnAlreadyFinishedWorkfunction bailoutOnAlreadyFinishedWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,): Fiber | null { if (current !== null) { // 复用上一次的依赖 workInProgress.dependencies = current.dependencies; }
... // Check if the children have any pending work. if (!includesSomeLane(renderLanes, workInProgress.childLanes)) { ... return null; } else { // 复用孩子节点 cloneChildFibers(current, workInProgress); return workInProgress.child; }}
// propagateContextChangeexport function propagateContextChange( workInProgress: Fiber, context: ReactContext<mixed>, changedBits: number, renderLanes: Lanes,): void { // 获得孩子节点信息 let fiber = workInProgress.child;
if (fiber !== null) { // 有孩子的话,就去关联他的父亲 fiber.return = workInProgress; } while (fiber !== null) { let nextFiber;
// Visit this fiber. const list = fiber.dependencies; // 遍历fiber
if (list !== null) { nextFiber = fiber.child; // 下一个处理的就是fiber的孩子,深度优先
let dependency = list.firstContext; while (dependency !== null) { // 遍历dependency链表 if ( // context变化了 dependency.context === context && (dependency.observedBits & changedBits) !== 0 ) {
if (fiber.tag === ClassComponent) { // 强制调度,创建更新 const update = createUpdate( NoTimestamp, pickArbitraryLane(renderLanes), ); update.tag = ForceUpdate; // 加入更新队列 enqueueUpdate(fiber, update); } // 合并context更新任务的优先级与fiber渲染优先级 fiber.lanes = mergeLanes(fiber.lanes, renderLanes); const alternate = fiber.alternate;
if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, renderLanes); } // 更新爷爷级别的优先级 scheduleWorkOnParentPath(fiber.return, renderLanes);
... break; } dependency = dependency.next; } } else if (fiber.tag === ContextProvider) { ... } else if ( enableSuspenseServerRenderer && fiber.tag === DehydratedFragment ) { ... } fiber = nextFiber; }}
复制代码
updateContextConsumer
updateContextConsumer中,执行prepareToReadContext判断优先级是否足够加入当前这次render,readContext取到当前context的value。
function updateContextConsumer( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes,) { let context: ReactContext<any> = workInProgress.type; ... const newProps = workInProgress.pendingProps; const render = newProps.children;
... // 比一比谁的优先级高,能不能加入到渲染队列里面去 prepareToReadContext(workInProgress, renderLanes); // 读取新的value const newValue = readContext(context, newProps.unstable_observedBits);
let newChildren; ...
// React DevTools reads this flag. workInProgress.flags |= PerformedWork; // diff reconcileChildren(current, workInProgress, newChildren, renderLanes); return workInProgress.child;}
复制代码
所以useContext的执行流程大致如下:
属性相关的 hook
对于写原生的朋友来讲,获取一个 dom 节点直接用document.getElementByXxx,是多么的舒服,react也提供了一种获取节点的hook -- useRef eg:
const App = () => { const inputValue = useRef(null);
const getValue = () => { console.log(inputValue.current.value) }
return ( <> <input ref={inputValue} type="text" /> <button onClick={getValue}>Click</button> </> )}
复制代码
在input框中输入文字,点击按钮则可以在控制台获取到输入的文字。对比于原生的,它的优势在于省略掉了那些获取操作。useRef如此简单,还是一起来看一下源码吧。根据以往经验,我们找到mountRef函数。
mountRef
function mountRef<T>(initialValue: T): {|current: T|} { const hook = mountWorkInProgressHook();// 获取hook链表
const ref = {current: initialValue}; // ref是一个对象 <X ref={xxx} />
if (__DEV__) { Object.seal(ref); } // 存入到hook.memoizedState中 hook.memoizedState = ref; // 返回 {current: initialValue} 获取通常用x.current return ref;}
复制代码
更新阶段的 useRef
function updateRef<T>(initialValue: T): {|current: T|} { const hook = updateWorkInProgressHook();// 获取更新的hook return hook.memoizedState; // hook返回}
复制代码
useRef 的执行流程
我们一猜就知道肯定是在某个阶段对具有ref标记的属性标签,进行了某些处理,肯定是render阶段里面做的,我们去render的beginWork里面去找,果然发现 markRef函数。
markRef
const ref = workInProgress.ref; if ( (current === null && ref !== null) || (current !== null && current.ref !== ref) ) { // Schedule a Ref effect workInProgress.flags |= Ref; }}
复制代码
但是在查找的过程中,我们还发现位于completeWork函数中也出现了markRef的身影。
function markRef(workInProgress: Fiber) { workInProgress.flags |= Ref;}
复制代码
在这里面做的事情就是给带有ref属性的标签打上标记,嗯?打上标记干什么呢,那肯定是在commit阶段去处理啊!那么我们再去找一下commit阶段的commitMutationEffects函数,果不其然,
function commitMutationEffects( root: FiberRoot, renderPriorityLevel: ReactPriorityLevel,) { ... // flags == 'Ref' ,更新ref current if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); //移除ref } }
...}
复制代码
在这个函数里面我们发现了对Ref的处理:
commitDetachRef
function commitDetachRef(current: Fiber) { const currentRef = current.ref; //获得当前的ref
if (currentRef !== null) { // 如果ref是函数则执行 if (typeof currentRef === 'function') { currentRef(null); } else { // 否则则返回{current:null} currentRef.current = null; } }}
复制代码
commitAttachRef
function commitAttachRef(finishedWork: Fiber) { const ref = finishedWork.ref; if (ref !== null) { const instance = finishedWork.stateNode; // 获得ref实例
let instanceToUse; switch (finishedWork.tag) { case HostComponent: instanceToUse = getPublicInstance(instance); break; default: instanceToUse = instance; }
// Moved outside to ensure DCE works with this flag if (enableScopeAPI && finishedWork.tag === ScopeComponent) { instanceToUse = instance; } if (typeof ref === 'function') { // 赋值 ref(instanceToUse); } else { ... ref.current = instanceToUse; } }}
复制代码
所以useRef大致执行流程如下:
总结
本文主要讲解了部分hooks的使用与原理,对hook使用更加熟悉了,还有一部分React内置hook的使用请查看官网,还有基于React的扩展 ahooks 都是值得学习的
评论