React暴露出来的部分Hooks
代码语言:javascript复制//packages/react/src/React.js
export {
...
useCallback,
useContext,
useEffect,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
...
}
功能描述
useState
、useReducer
: 状态值相关useEffect
、useLayoutEffect
: 生命周期相关useContext
: 状态共享相关useCallback
、useMemo
: 性能优化相关useRef
: 属性相关
源码
代码语言:javascript复制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);
}
// resolveDispatcher
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
invariant(
...
);
return dispatcher;
}
// ReactCurrentDispatcher
const ReactCurrentDispatcher = {
/** * @internal
* @type {ReactComponent} */
current: (null: null | Dispatcher),
};
其实hooks
的定义都来自dispatcher
,那我们根据Dispatcher
依次去看看他们的实际实现。
Dispatcher
代码语言:javascript复制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.js
function 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
代码语言:javascript复制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;
}
}
- 如果是
mount
阶段,则执行HooksDispatcherOnMount
,之后再执行reconcileChildren
。 - 如果是
update
阶段,则执行HooksDispatcherOnUpdate
,之后再执行reconcileChildren
。
HooksDispatcherOnMount
代码语言:javascript复制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
代码语言:javascript复制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
代码语言:javascript复制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
执行的时候会触发渲染更新
,以及二次更新
,那么他是怎么实现的呢?
针对于上述问题,我们必须去看一下dispatch
到底干了什么事情。
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
dispatchAction
代码语言:javascript复制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阶段
代码语言:javascript复制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
代码语言:javascript复制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的用法
代码语言:javascript复制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
代码语言:javascript复制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
,面试中经常会问到这两个有什么区别,其实他们并没有什么区别,只是执行的时机不同,具体表现为:
useLayoutEffect
是在渲染的时候同步执行的,且与componentDidMount
、componentDidUpdate
执行时机一致,所以在commit
阶段中,执行于commitLayoutEffec
函数里。useEffect
执行是异步的,要等到组件渲染到屏幕上才会去执行。
操作dom性能相关问题为什么修改dom建议放在useLayoutEffect中而不是useEffect中呢?
- 从上述表述中可以看出,只要真实
dom
被创建,就会执行useLayoutEffect
函数,而我们知道在创建真实dom
的时候,节点的属性,样式等等都会被挂到dom
节点上面去,一个节点增加属性必定会引起页面的重绘
,重排
。而useEffect
执行时机实在dom
渲染到屏幕上
面之后,这个时候已经经历过了一次重绘
,重排
。如果再一次的修改dom
必定引起二次重绘
、重排
,这样来说,性能开销
变成了两倍。所以为了减少性能开销,才如此建议。废话少说来看看useEffect
的具体实现,mount
阶段对应的是mountEffect
。
mountEffect
代码语言:javascript复制function mountEffect(
// 传入的函数useEffect(()=>{},[])
create: () => (() => void) | void, // 依赖
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
...
}
}
return mountEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
}
mountEffect
调用mountEffectImpl
mountEffectImpl
代码语言:javascript复制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
代码语言:javascript复制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
:
// updateEffect
function updateEffect(
// 传入函数
create: () => (() => void) | void, // 依赖项
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
...
}
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
}
//updateEffectImpl
function 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
代码语言:javascript复制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);
}
}
// enqueuePendingPassiveHookEffectMount
export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber, effect: HookEffect,
): void {
pendingPassiveHookEffectsMount.push(effect, fiber);
if (!rootDoesHavePassiveEffects) {
// root上有没有副作用,默认值为false
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
// enqueuePendingPassiveHookEffectUnmount
export 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
代码语言:javascript复制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
在讲这一部分之前我们应该搞明白一个问题
- 为什么要用
useMemo
、useCallback
来做性能优化 useMemo
、useCallback
应该怎么做性能优化- 具体场景是什么
先回答第一个问题,为什么要用他们来做性能优化: 我们要知道,React
更新流程中只要组件中props
或者state
发生了变化,那就是必须从变化顶部更新所有的后代组件,牵一发而动全身。有些组件我们并不想要它重新渲染
,它却做了一定量的性能牺牲,用useMemo
、useCallback
就可以改变这样的局面。那么应该怎么用他们来做性能优化呢,主要体现在三个方面:
- 避免无效的副作用
- 避免无效的累计计算
- 避免无效的重新渲染
因为我们前面讲到useEffect
,我们可以期望在依赖发生变更的时候去做我们想要做的事情,例如接口请求
const App = () => {
// 只有当parmas发生变化的时候,我们才回去执行fetch()
useEffect(()=>{
fetch()
}, [params])
}
这并不是他们俩的功能呢,没关系,我们可以变着法子让他们俩具有类似功能,不过这就有点牵强了。
那怎么去做无效的计算和无效的重复渲染呢?有这样的一道面试题:
代码语言:javascript复制// 点击父组件里面的按钮,会不会在子组件里面打印“子组件渲染了”?如果会,该怎么优化?
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>;
//});
那么避免无效的计算体现在哪里呢:
代码语言:javascript复制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
他都是不一样的。针对于这种问题解决方式:
- 子组件写成
class
组件,继承PureComponent
(不推荐) - 子组件写成函数组件,用
memo
包裹起来。
因为PureComponent
里面会有一个shouldComponentUpdate
来处理props
的浅比较,而memo
则也是我们现在需要去探索的东西。所以对于useCallback的使用一定要配合上具有浅比较的组件使用,否则不能优化性能,反而浪费性能。
useMemo
适用于大量的计算得出的结果,防止在组建更新的时候,避免不必要的重新计算。
说了这么多,我们还是一起来看看他们是如何实现的吧,根据上面的经验,我们可以在HooksDispatcherOnMount
里面找到mountCallback
和mountMemo
:
mountCallback、mountMemo
代码语言:javascript复制// mountCallback
function 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; // 返回传入的函数
}
// mountMemo
function 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
代码语言:javascript复制// updateCallback
function 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;
}
//updateMemo
function 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 决定。
useContext
可以帮助我们跨越组件层级直接传递变量,实现数据共享。Context
的作用就是对它所包含的组件树提供全局共享数据的一种技术。
听的云里雾里的?不要担心,说白了就是组件之间传值就完事了,那么你又说了,组件传值我们用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提供了,直接拿的功能,例如:
代码语言:javascript复制// 用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
代码语言:javascript复制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
,先来解释一下他们两个的作用吧。
context.Provider
的作用在于规定上下文范围,并提供组件所需要的状态。context.Consumer
的作用在于接收传输过来的状态。
eg:
代码语言:javascript复制// ComponentA
const ComponentA = () => {
const [count, setCount] = useState(0);
return (
<div>
<h4>父级:{count}</h4>
<Content.Provider value={{count,setCount}}>
<ComponentB />
</Content.Provider>
</div>
)
}
// ComponentB
const 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.js
const valueCursor: StackCursor<mixed> = createCursor(null);
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
代码语言:javascript复制//ReactFiberStack.old.js
const valueStack: Array<any> = [];
- 在
render
阶段的beginWork
函数里调用updateContextProvider
的时候会执行pushProvider
,将新的值push
进valueStack
中 - 在
render
阶段的completeWork
函数里调用popProvider
,将栈顶context
pop
出来
//pushProvider
function pushProvider(providerFiber, nextValue) {
var context = providerFiber.type._context;
{
push(valueCursor, context._currentValue, providerFiber);
context._currentValue = nextValue;
}
}
//popProvider
function 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
代码语言:javascript复制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
代码语言:javascript复制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
去更新当前节点信息。
// bailoutOnAlreadyFinishedWork
function 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;
}
}
// propagateContextChange
export 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
代码语言:javascript复制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
代码语言:javascript复制function updateRef<T>(initialValue: T): {|current: T|} {
const hook = updateWorkInProgressHook();// 获取更新的hook
return hook.memoizedState; // hook返回
}
useRef的执行流程
我们一猜就知道肯定是在某个阶段对具有ref
标记的属性标签,进行了某些处理,肯定是render
阶段里面做的,我们去render
的beginWork
里面去找,果然发现 markRef
函数。
markRef
代码语言:javascript复制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
的处理:
- 如果
Ref
改变了,且不为null
则执行commitDetachRef
删掉ref
,之后并在commitLayoutEffect
中执行commitAttachRef
更新ref
的值。
commitDetachRef
代码语言:javascript复制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
代码语言:javascript复制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都是值得学习的