React:
react hook
React.js
import {
useContext,
useEffect,
useState,
} from './ReactHooks'我们看看这三个函数具体源码实现
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, inputs);
}
export function useContext<T>(
Context: ReactContext<T>,
observedBits: number | boolean | void,
) {
const dispatcher = resolveDispatcher();
// dev code
return dispatcher.useContext(Context, observedBits);
}Hooks在React中的源码,可见他们也跟其他React的API一样,只管定义,不管实现。他们都调用了ReactCurrentOwner.currentDispatcher.xxx对应的方法。那么这个ReactCurrentOwner.currentDispatcher是啥呢?
在我们执行renderRoot开始渲染的时候,我们会设置这个值:
import {Dispatcher} from './ReactFiberDispatcher';
if (enableHooks) {
ReactCurrentOwner.currentDispatcher = Dispatcher;
}并且在离开renderRoot的时候设置为null。那么这个Dispatcher是什么呢?
// ReactFiberDispatcher.js
import {readContext} from './ReactFiberNewContext';
import {
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
} from './ReactFiberHooks';
export const Dispatcher = {
readContext,
useCallback,
useContext,
useEffect,
useImperativeMethods,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
};OK,Hooks方法的源头代码找到了。需要注意的是Hooks只有FunctionalComponent被更新的时候才会被调用,所以我们肯定需要关心一下FunctionalComponent的更新过程。
Hooks方法的源头代码:需要注意的是Hooks只有FunctionalComponent被更新的时候才会被调用,所以我们肯定需要关心一下FunctionalComponent的更新过程。
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
nextChildren = Component(nextProps, context);
nextChildren = finishHooks(Component, nextProps, nextChildren, context);
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}可以看到这里增加了三个方法调用:prepareToReadContext,prepareToUseHooks,finishHooks。后两个都明显跟Hooks有关,而第一个是读取新的Context API的,因为在Hooks中有读取Context的操作,所以增加这个就无可厚非了。我们看一下prepareToUseHooks和finishHooks分别做了什么。
prepareToUseHooks
这个方法来自ReactFiberHooks.js,明显只是初始化一些模块内的全局变量。
export function prepareToUseHooks(
current: Fiber | null,
workInProgress: Fiber,
nextRenderExpirationTime: ExpirationTime,
): void {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
firstCurrentHook = current !== null ? current.memoizedState : null;
}
finishHooks
相比之下finishHooks则复杂很多,一进来就有一个while循环,这个循环是因为Hooks提供了我们一个功能:如果在一次更新中(也就是调用FunctionalComponent的过程中)如果直接调用了类似setState的Hooks API产生了新的更新,则会在当前的渲染周期中直接执行更新。这个大家可以先了解一下,后面我们具体讲Hooks的实现会再具体讲到。
后面设置了renderedWork.updateQueue,就非常类似于HostComponent和ClassComponent了,本来FunctionalComponent在commit阶段是完全没有更新的,但是现在Hooks给了他产生side effect的能力,所以这就是记录这些side effect的queue。
后面就是初始化全局变量了,就先不多讲了。
export function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
while (didScheduleRenderPhaseUpdate) {
didScheduleRenderPhaseUpdate = false;
numberOfReRenders += 1;
// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;
componentUpdateQueue = null;
children = Component(props, refOrContext);
}
renderPhaseUpdates = null;
numberOfReRenders = 0;
const renderedWork: Fiber = (currentlyRenderingFiber: any);
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
renderedWork.updateQueue = (componentUpdateQueue: any);
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
firstCurrentHook = null;
currentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
return children;
}useState
useState
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}useState
useReducer
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();第一次渲染
// There's no existing queue, so this is the initial render.
if (reducer === basicStateReducer) {
// Special case for `useState`.
if (typeof initialState === 'function') {
initialState = initialState();
}
} else if (initialAction !== undefined && initialAction !== null) {
initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;queue = workInProgressHook.queue = {
last: null,
dispatch: null,
};state的方法的,接下去我们就要创建dispatch方法了
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));更新
分两种情况,是否是reRender,所谓reRender就是说在当前更新周期中又产生了新的更新,就继续执行这些更新知道当前渲染周期中没有更新为止他们基本的操作是一致的,就是根据reducer和update.action来创建新的state,并赋值给Hook.memoizedState以及Hook.baseState。注意这里,非对于reRender得情况,我们会对每个更新判断其优先级,如果不是当前整体更新优先级内得更新会跳过,一个第得展示跳过Update会变成新的baseUpdate,他记录了在之后所有得Update,即使是优先级比他高得,因为在他被执行得时候,需要保证后续的更新要在他更新之后的基础上再次执行,因为结果可能会不一样。
太累了 歇歇 喘口气