sourceCode
overall
React 要做的事情,其实就是数据驱动分块渲染。如果单想做分块渲染很简单,在初次渲染时,可以知道每个 dom 节点和对应的数据/函数组件,数据变动时,重新调用函数组件生成新的 vdom 节点,再重新生成对应的 dom 节点。难点在于数据变动生成新节点的过程需要打碎异步进行,这就面临着调度及其优化。
大致流程图
第一次执行:
update:
lane
Just like lanes on the road, the rule is to drive on different lanes based on your speed, meaning the smaller the lane is the more urgent the work is, the higher priority it is. so SyncLane is 1 here.
bitwise operation
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
return (set & subset) === subset;
}
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
related methods
requestUpdateLane(fiber):lane
priority 相关
React 为不同的操作设置了不同的优先级,比如点击、input输入为同步优先级
避免饥饿:设置过期时间;高优先级任务插队
anything
:hatched_chick: React.createElement
// used by @babel/plugin-transform-react-jsx
:hatched_chick: React.createRef:
// an immutable object with a single mutable value
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject);
}
return refObject;
}
Fiber:
// Dependencies (contexts, events) for this fiber, if it has any
fiber.dependencies: Dependencies | null
形式上是链表,这样可以记住暂停的位置;像之前的递归算法就得一口气算到头
hooks
useHooks only called when function firstly called
mountWorkInProgressHook()
: 构造 workInProgressHook, 以链表形式保存 hook
updateWorkInProgressHook()
: 返回 hook,若是链表头,则从 currentlyRenderingFiber.memoizedState 中读取 hook 复制
hooks can only called within the function
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
invariant(
dispatcher !== null,
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
return dispatcher;
}
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
//there are some more dispatchers for other state
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useOpaqueIdentifier: throwInvalidHookError,
unstable_isNewReconciler: enableNewReconciler,
};
// In renderWithHooks(), after Component() call executed
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
other middle variables
ReactCurrentOwner.current = workInProgress;
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
// This dispatcher handles an edge case where a component is updating,
// but no stateful hooks have been used.
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
// This dispatcher does that.
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
hooks cant be nested
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
mountHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
useState
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
// every hooks would be listed 链表
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue, // 作用在存储多次 dispatch
): any)); // 初始化时的 setXXX 就是 function dispatchAction, 这里绑定了 fiber
return [hook.memoizedState, dispatch];
}
通过 dispatchAction 存入新值时,如何触发 update 或组件函数的重新渲染呢?
dispatchAction() ->
-
const lane = requestUpdateLane(fiber); // 根据 fiber 的 mode, 选择不同优先级的 lane 返回
-
queue.pending = update // update.lane update.action(要触发的动作) update.next(回环链表)
- scheduleUpdateOnFiber(fiber, lane, eventTime) ->
- performSyncWorkOnRoot(root) or schedulePendingInteractions(root, lane);
useEffect
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();
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;
}
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,
);
}
那什么时候执行 effects 里的回调呢?flushPassiveEffects(), 主要在 commit phase 的 commitRoot() 里