lz-lee / react-source-code Goto Github PK
View Code? Open in Web Editor NEWReact源码阅读笔记
React源码阅读笔记
承担了唯二可以更新应用的api: setState
、forceUpdate
首次渲染 instance
为 null
constructClassInstance
创建实例mountClassInstance
挂载实例,调用 willMount
getDerivedStateFromProps
则会执行并更新 state
,并且不会执行 willMount
钩子didMount
则标记 workInProgress
的 effectTag
为 Update
渲染被中断 instance !== null
, current === null
resumeMountClassInstance
复用实例但还是调用首次渲染的生命周期方法
const hasNewLifecycles =
typeof getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function';
getDerivedStateFromProps
则会执行并更新 state
hasNewLifecycles
为 true
, 则不会调用 componentWillReceiveProps
和 willMount
钩子,否则先调用 componentWillReceiveProps
再调用 componentWillMount
didMount
则标记 workInProgress
的 effectTag
为 Update
更新渲染 instance !== null
, current !== nul
l
updateClassInstance
,调用 componentWillReceiveProp
和 willUpdate
生命周期
getDerivedStateFromProps
则会执行并更新 state
hasNewLifecycles
为 true
则 componentWillReceiveProp
和 willUpdate
不会调用shouldUpdate
为 true
,并有 didUpdate
方法则标记 workInProgress
的effectTag
为 Update
初次渲染 updateUqueue
为 null
,当有 setState
时 updateQueue
可能有多个 update
,需要调用 processUpdateQueue
更新 state
最后执行 finishClassComponent
reconcileChildren
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// ...context 相关
// classComponent 通过 new class 获取的实例对象
const instance = workInProgress.stateNode;
// 通过这个变量判断是否更新component
let shouldUpdate;
// 首次 render 时不存在,或者class组件第一次渲染
if (instance === null) {
// 如果非首次渲染,而 instance 不存在,表示的是 suspended 异步组件的情况
if (current !== null) {
// An class component without an instance only mounts if it suspended
// inside a non- concurrent tree, in an inconsistent state. We want to
// tree it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial pass we might need to construct the instance.
// 创建 instance
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 挂载 class组件
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else if (current === null) {
// instance !== null,之前首次挂载时被中断的,创建了 instance 但在 render 中报错的情况
// In a resume, we'll already have an instance we can reuse.
// 复用 instance
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
} else {
// 更新渲染
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = null;
const contextType = ctor.contextType;
// context 相关
// ctor 就是 Reactelemnt.type 即声明组件的组件名,这里生成组件实例
const instance = new ctor(props, context);
// 初始化 state
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
// 为 instance 添加 updater 为 classComponentUpdater,
// 指定 workInProgress 的 stateNode 为当前instance
// ReactInstanceMap map 对象, 给 instance._reactInternaleFiber 赋值当前 workInprogress
adoptClassInstance(workInProgress, instance);
// 创建实例时的警告,使用 getDerivedStateFromProps 不推荐设置 state 为 null
// 使用了 getDerivedStateFromProps 或者getSnapshotBeforeUpdate, willXXXX 声明周期方法不会执行
if (__DEV__) {
// ...
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// ReactFiberContext usually updates this cache but can't for newly-created instances.
// context 相关
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
// adoptClassInstance 方法
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// The instance needs access to the fiber so that it can schedule updates
ReactInstanceMap.set(instance, workInProgress);
if (__DEV__) {
instance._reactInternalInstance = fakeInternalInstance;
}
}
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
}
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
instance.context = readContext(contextType);
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
}
if (__DEV__) {
// ....
}
// 初次渲染 updateUqueue 为 null,当有 setState 时 updateQueue 可能有多个 update ,需要调用 processUpdateQueue 更新 state
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
// 更新 instance 上的 state
instance.state = workInProgress.memoizedState;
}
// 新的声明周期,组件更新过程中调用,即拿到的是更新后的 state
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
// 更新 state
instance.state = workInProgress.memoizedState;
}
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// 用了新的 API 则 willMount 不会执行
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
// willMount 可能执行了 setState 因此需要执行 processUpdateQueue 来更新 state
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
// process them now.
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
}
// 如果有 didMount,标记 effect 为 update,在 commit 阶段时,表明组件是有 didmount 这个方法的,
// 等到组件内容真正 mount 到 dom 上时,才能真正调用 didmount 这个方法,等到需要用的时候才会去调用
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
都是基于 queue.baseState
来计算新的 state
的,遍历 updateQueue 过程中会将每个 update 的更新优先级与当前更新优先级做对比,如果低于当前更新优先级则不执行
没有剩下来的 update 和 captureUpdate 表示全部计算完了,更新 baseState 为计算后的 resultState
export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;
// 保证 workInProgress 上的 updateQueue 是 clone 的 queue,防止直接修改 current上的 queue
queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);
if (__DEV__) {
currentlyProcessingQueue = queue;
}
// These values may change as we process the queue.
// 每次执行完 processUpdateQueue 都会把新的 state 赋值到 baseState 上
let newBaseState = queue.baseState;
let newFirstUpdate = null;
let newExpirationTime = NoWork;
// Iterate through the list of updates to compute the result.
let update = queue.firstUpdate;
let resultState = newBaseState;
while (update !== null) {
// update的执行优先级
const updateExpirationTime = update.expirationTime;
// 优先级低则不执行
if (updateExpirationTime > renderExpirationTime) {
// This update does not have sufficient priority. Skip it.
if (newFirstUpdate === null) {
// This is the first skipped update. It will be the first update in
// the new list.
// 此 update 就成为剩下的第一个待更新的
newFirstUpdate = update;
// Since this is the first update that was skipped, the current result
// is the new base state.
newBaseState = resultState;
}
// Since this update will remain in the list, update the remaining
// expiration time.
if (
newExpirationTime === NoWork ||
newExpirationTime > updateExpirationTime
) {
// 不更新则赋值为此 update 的过期时间
newExpirationTime = updateExpirationTime;
}
} else {
// This update does have sufficient priority. Process it and compute
// a new result.
// 这次 update 要被执行, setState 的 update.tag -> UpdateState -> 计算新的 state
// 如果为 forceUpdate 则更新全局变量 hasForceUpdate = true
resultState = getStateFromUpdate(
workInProgress,
queue,
update,
resultState, // prevState 为之前计算的 baseState
props,
instance,
);
// setState(fn ,callback) callback 为第二个参数,在 setState 之后执行
const callback = update.callback;
if (callback !== null) {
// 标记 effect 在 commit 阶段,setState 更新完成之后执行
workInProgress.effectTag |= Callback;
// Set this to null, in case it was mutated during an aborted render.
// 防止中断的 render 过程被修改
update.nextEffect = null;
// 保存链表结构
if (queue.lastEffect === null) {
queue.firstEffect = queue.lastEffect = update;
} else {
queue.lastEffect.nextEffect = update;
queue.lastEffect = update;
}
}
}
// 操作下一个 update
// Continue to the next update.
update = update.next;
}
// 同样的对 captureUpdate 执行更新操作
// Separately, iterate though the list of captured updates.
let newFirstCapturedUpdate = null;
update = queue.firstCapturedUpdate;
while (update !== null) {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime > renderExpirationTime) {
// This update does not have sufficient priority. Skip it.
if (newFirstCapturedUpdate === null) {
// This is the first skipped captured update. It will be the first
// update in the new list.
newFirstCapturedUpdate = update;
// If this is the first update that was skipped, the current result is
// the new base state.
if (newFirstUpdate === null) {
newBaseState = resultState;
}
}
// Since this update will remain in the list, update the remaining
// expiration time.
if (
newExpirationTime === NoWork ||
newExpirationTime > updateExpirationTime
) {
newExpirationTime = updateExpirationTime;
}
} else {
// This update does have sufficient priority. Process it and compute
// a new result.
resultState = getStateFromUpdate(
workInProgress,
queue,
update,
resultState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.effectTag |= Callback;
// Set this to null, in case it was mutated during an aborted render.
update.nextEffect = null;
if (queue.lastCapturedEffect === null) {
queue.firstCapturedEffect = queue.lastCapturedEffect = update;
} else {
queue.lastCapturedEffect.nextEffect = update;
queue.lastCapturedEffect = update;
}
}
}
update = update.next;
}
if (newFirstUpdate === null) {
queue.lastUpdate = null;
}
if (newFirstCapturedUpdate === null) {
queue.lastCapturedUpdate = null;
} else {
workInProgress.effectTag |= Callback;
}
// 没有剩下来的 update,表示全部计算完了
if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
// We processed every update, without skipping. That means the new base
// state is the same as the result state.
// 重新赋值 newBaseState 为最后 计算的state
newBaseState = resultState;
}
// 赋值 baseState 为计算后的 state
queue.baseState = newBaseState;
queue.firstUpdate = newFirstUpdate;
queue.firstCapturedUpdate = newFirstCapturedUpdate;
// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
// 需要执行的更新已经更新完了,剩下的更新应该是剩下的 updateQueue 里面的 update 中最高优先级的那个 expirationTime
workInProgress.expirationTime = newExpirationTime;
// 更新 state
workInProgress.memoizedState = resultState;
if (__DEV__) {
currentlyProcessingQueue = null;
}
}
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
case ReplaceState: {
// ...
}
case CaptureUpdate: {
// ...
}
// Intentional fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
if (__DEV__) {
// ...
}
// 函数更新 state
partialState = payload.call(instance, prevState, nextProps);
} else {
// 普通对象
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
// 最后合并原 state 和计算出的新 state
return Object.assign({}, prevState, partialState);
}
//
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
export function applyDerivedStateFromProps(
workInProgress: Fiber,
ctor: any,
getDerivedStateFromProps: (props: any, state: any) => any,
nextProps: any,
) {
// 这里的 state 是更新过后的state
const prevState = workInProgress.memoizedState;
// 传入的 state 是经过 processUpdateQueue 计算后的state
const partialState = getDerivedStateFromProps(nextProps, prevState);
// Merge the partial state and the previous state.
const memoizedState =
partialState === null || partialState === undefined
? prevState
: Object.assign({}, prevState, partialState);
// 更新state
workInProgress.memoizedState = memoizedState;
// Once the update queue is empty, persist the derived state onto the
// base state.
const updateQueue = workInProgress.updateQueue;
// queue 不为null 且 不渲染的时候,才更新 baseState
if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
updateQueue.baseState = memoizedState;
}
}
function resumeMountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
const oldProps = workInProgress.memoizedProps;
instance.props = oldProps;
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
const hasNewLifecycles =
typeof getDerivedStateFromProps === 'function' ||
typeof instance.getSnapshotBeforeUpdate === 'function';
// Note: During these life-cycles, instance.props/instance.state are what
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// 有新 API则不执行 componentWillReceiveProps
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function')
) {
if (oldProps !== newProps || oldContext !== nextContext) {
callComponentWillReceiveProps(
workInProgress,
instance,
newProps,
nextContext,
);
}
}
// => hasForceUpdate = false
resetHasForceUpdateBeforeProcessing();
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
newState = workInProgress.memoizedState;
}
if (
// 新老 props 和 state 都相同
oldProps === newProps &&
oldState === newState &&
// 没有 context 变化
!hasContextChanged() &&
// 如果 hasForceUpdate 为 false
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
// 如果有 didMount 则标记 effect 为 update
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
// return 赋值给了 shouldUpdate, 组件是不需要更新的
return false;
}
// 执行新API
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
newState = workInProgress.memoizedState;
}
const shouldUpdate =
// 如果 hasForceUpdate 为 true
checkHasForceUpdateAfterProcessing() ||
// 或者 根据 shouldComponentUpdate 的返回值 或者 pureComponent的情况则调用 shallowEqual 的返回值
// 默认返回 true
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
if (shouldUpdate) {
// 需要更新组件
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// 没有使用新声明周期并有 willMount 则调用 willMount
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
startPhaseTimer(workInProgress, 'componentWillMount');
if (typeof instance.componentWillMount === 'function') {
instance.componentWillMount();
}
if (typeof instance.UNSAFE_componentWillMount === 'function') {
instance.UNSAFE_componentWillMount();
}
stopPhaseTimer();
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
// 都需要标记 didMount ,中断的组件挂载仍然按首次挂载执行
} else {
// 不需要更新
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
// If shouldComponentUpdate returned false, we should still update the
// memoized state to indicate that this work can be reused.
// 组件不需要更新,仍然更新 memoized 变量
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
// Update the existing instance's state, props, and context pointers even
// if shouldComponentUpdate returns false.
// instance 上的 state、props、context 始终要更新
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
// shouldComponentUpdate 来判断
if (typeof instance.shouldComponentUpdate === 'function') {
startPhaseTimer(workInProgress, 'shouldComponentUpdate');
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
stopPhaseTimer();
return shouldUpdate;
}
// 如果是pureComponent 则浅对比 props 和 state
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
// shallowEqual
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* inlined Object.is polyfill to avoid requiring consumers ship their own
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
*/
// polyfill Object.is()
function is(x, y) {
// SameValue algorithm
if (x === y) {
// Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
// Added the nonzero y check to make Flow happy, but it is redundant
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
}
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
resumeMountClassInstance
相似,不同的是执行的声明周期是 willUpdate
,标记 didUpdate
和 getSnapshotBeforeUpdate
if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
// Unsafe lifecycles should not be invoked for components using the new APIs.
// 未使用新API ,执行 componentWillUpdate
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function')
) {
startPhaseTimer(workInProgress, 'componentWillUpdate');
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
stopPhaseTimer();
}
// 标记 didUpdate
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.effectTag |= Update;
}
// 标记 getSnapshotBeforeUpdate
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.effectTag |= Snapshot;
}
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (typeof instance.componentDidUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
oldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.effectTag |= Snapshot;
}
}
// If shouldComponentUpdate returned false, we should still update the
// memoized props/state to indicate that this work can be reused.
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
getDerivedStateFromError
方法,直接执行 instance.render()
获得最新的 nextChildren
, 否则 nextChildren
为 null
,getDerivedStateFromError
方法可以在出错后立即生成 state
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
// 标记 ref,仅有在 classComponent 和 HostComponent (原生标签)中标记ref,即只能在这两种类型上 使用 ref
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
// 不需要更新且没有错误则跳过更新
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
// 跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// getDerivedStateFromError 方法可以在出错后立即生成 state,防止出错后没有 instance 和 ref 的错误
// 而 componentDidCatch 是通过 setState 的,要在下次更新才生成新的 state
// 有错误,且没有 getDerivedStateFromError 方法 则直接渲染空
// If we captured an error, but getDerivedStateFrom catch is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
// 没错误
// 或者有 getDerivedStateFromError 方法,此方法已经更新了最新的state
// 通过instance.render 获取最新children
if (__DEV__) {
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = instance.render();
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
instance.render();
}
ReactCurrentFiber.setCurrentPhase(null);
} else {
nextChildren = instance.render();
}
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
// 非首次渲染且有错误
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
// 强制的重新计算 child 并 reconcileChildFibers 调和
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
} else {
// 没错误或者首次渲染,直接调和
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}
// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
workInProgress.memoizedState = instance.state;
// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
// 返回 child -> performUnitOfWork -> workLoop 循环
return workInProgress.child;
}
在 requestWork
中中如果判断是异步调度的方法就会执行 scheduleCallbackWithExpirationTime
// requestWork
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// ...
if (expirationTime === Sync) { // 同步的调用 js 代码
performSyncWork();
} else { // 异步调度 独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
scheduleCallbackWithExpirationTime(root, expirationTime); // 在 secheduler 文件夹下的单独模块
}
}
function scheduleCallbackWithExpirationTime(
root: FiberRoot,
expirationTime: ExpirationTime,
) {
// callbackExpirationTime 是上一次调度的任务优先级
if (callbackExpirationTime !== NoWork) {
// A callback is already scheduled. Check its expiration time (timeout).
// 当前优先级比之前正在执行的优先级低就停止
if (expirationTime > callbackExpirationTime) {
// Existing callback has sufficient timeout. Exit.
return;
} else {
// 当前优先级更高 则取消原来那个
if (callbackID !== null) {
// Existing callback has insufficient timeout. Cancel and schedule a
// new one.
cancelDeferredCallback(callbackID);
}
}
// The request callback timer is already running. Don't start a new one.
} else {
startRequestCallbackTimer();
}
// 保存当前任务优先级
callbackExpirationTime = expirationTime;
// originalStartTimeMs 是 react 加载的最初时间, 记录当前时间差
const currentMs = now() - originalStartTimeMs;
// expirationTime(到期时间为将来的某个时间)转化成 ms
const expirationTimeMs = expirationTimeToMs(expirationTime);
// 当前任务的过期时间, 到期时间减去当前任务开始调度的时间差,则为过期时间,如果timeout < 0 ,则表示任务过期,需要强制更新
const timeout = expirationTimeMs - currentMs;
// 依赖 Scheduler 模块返回的id用来cancel
callbackID = scheduleDeferredCallback(performAsyncWork, {timeout});
}
scheduler
模块下 unstable_scheduleCallback
函数
// ReactDOMHostConfig.js
export {
unstable_now as now,
unstable_scheduleCallback as scheduleDeferredCallback,
unstable_cancelCallback as cancelDeferredCallback,
} from 'scheduler';
firstCallbackNode
是维护的双向链表结构的头部// callback -> performAsyncWork, deprecated_options -> { timeout }
function unstable_scheduleCallback(callback, deprecated_options) {
// 即 Date.now()
var startTime =
currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
var expirationTime;
if (
typeof deprecated_options === 'object' &&
deprecated_options !== null &&
typeof deprecated_options.timeout === 'number'
) {
// expirationTime 的逻辑将来可能全部移入到 Scheduler 包中, 目前只会进入这个判断
// FIXME: Remove this branch once we lift expiration times out of React.
expirationTime = startTime + deprecated_options.timeout;
} else {
switch (currentPriorityLevel) {
case ImmediatePriority:
expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
expirationTime = startTime + USER_BLOCKING_PRIORITY;
break;
case IdlePriority:
expirationTime = startTime + IDLE_PRIORITY;
break;
case NormalPriority:
default:
expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
}
}
var newNode = {
// performAsyncWork
callback,
// 目前用不到
priorityLevel: currentPriorityLevel,
// timeout + now()
expirationTime,
// 存储链表结构
next: null,
previous: null,
};
// firstCallbackNode 是用来维护的双向链表的头部
if (firstCallbackNode === null) {
// This is the first callback in the list.
firstCallbackNode = newNode.next = newNode.previous = newNode;
// firstCallbackNode 改变需要调用 进入调度过程
ensureHostCallbackIsScheduled();
} else {
// 如果有一个/多个 node 存在链表结构里
// next表示下一个要执行的node
var next = null;
// 原来的
var node = firstCallbackNode;
do {
// 根据 expirationTime 排序,把优先级高的放在前面,每次都跟 firstCallbackNode 比较
if (node.expirationTime > expirationTime) {
// The new callback expires before this one.
// 表示新的node的优先级高于原来的,将原来的(firstCallbackNode)赋值给 next, 退出循环
next = node;
break;
}
node = node.next;
} while (node !== firstCallbackNode);
// 如果 nex t为null,说明 node.expirationTime < expirationTime, 即新的node的优先级是最小的
if (next === null) {
// No callback with a later expiration was found, which means the new
// callback has the latest expiration in the list.
// next为原来的node
next = firstCallbackNode;
// 表示新的node的优先级 高于原来node的优先级,
} else if (next === firstCallbackNode) {
// The new callback has the earliest expiration in the entire list.
// 重新将 firstCallbackNode 赋值为新的node,即保证firstCallbackNode 为优先级最高的那个node
firstCallbackNode = newNode;
// firstCallbackNode 改变需要调用 进入调度过程
ensureHostCallbackIsScheduled();
}
// 原来 指向firstCallBakcNode.previous 和 指向firstCallBakcNode.next 都指向自己
// previous、next 都是指向firstCallBakcNode
var previous = next.previous;
// 现在将 都是指向firstCallBakcNode.previous 和 都是指向firstCallBakcNode.next 指向 newNode
previous.next = next.previous = newNode;
// 将 newNode.previous 和newNode.next 指向firstCallBakcNode, 形成环状双向链表结构
newNode.next = next;
newNode.previous = previous;
}
return newNode;
}
callbackNode
在执行,否则停止执行hostCallback
是否在调度,已经调度就取消requestHostCallback
function ensureHostCallbackIsScheduled() {
// 表示已经有一个 callbackNode 在调用了
if (isExecutingCallback) {
// Don't schedule work yet; wait until the next time we yield.
return;
}
// Schedule the host callback using the earliest expiration in the list.
var expirationTime = firstCallbackNode.expirationTime;
// 判断这个 hostCallback 有没有进入调度
if (!isHostCallbackScheduled) {
isHostCallbackScheduled = true;
} else {
// Cancel the existing host callback.
cancelHostCallback();
}
requestHostCallback(flushWork, expirationTime);
}
// callback 为 flushWork 函数
requestHostCallback = function(callback, absoluteTimeout) {
scheduledHostCallback = callback;
timeoutTime = absoluteTimeout;
// absoluteTimeout < 0 已经超时,直接强制执行
if (isFlushingHostCallback || absoluteTimeout < 0) {
// Don't wait for the next frame. Continue working ASAP, in a new event.
window.postMessage(messageKey, '*');
// isAnimationFrameScheduled = false 表示还没进入调度循环
} else if (!isAnimationFrameScheduled) {
// If rAF didn't already schedule one, we need to schedule a frame.
// TODO: If this rAF doesn't materialize because the browser throttles, we
// might want to still have setTimeout trigger rIC as a backup to ensure
// that we keep performing work.
isAnimationFrameScheduled = true;
// 进入调度, 竞争调用 animationTick
requestAnimationFrameWithTimeout(animationTick);
}
};
var ANIMATION_FRAME_TIMEOUT = 100;
var rAFID;
var rAFTimeoutID;
var requestAnimationFrameWithTimeout = function(callback) {
// schedule rAF and also a setTimeout
// requestAnimationFrame 执行则取消 setTimeout
rAFID = localRequestAnimationFrame(function(timestamp) {
// cancel the setTimeout
localClearTimeout(rAFTimeoutID);
callback(timestamp);
});
// 100ms 内 requestAnimationFrame 内的 callback 还未执行,则取消requestAnimationFrame,
rAFTimeoutID = localSetTimeout(function() {
// cancel the requestAnimationFrame
localCancelAnimationFrame(rAFID);
// 传入当前时间
callback(getCurrentTime());
}, ANIMATION_FRAME_TIMEOUT);
};
var frameDeadline = 0;
// We start out assuming that we run at 30fps but then the heuristic tracking
// will adjust this value to a faster fps if we get more frequent animation
// frames.
// 假设以 30fps 运行,然后进行跟踪,如果获得更频繁的帧,则会将此值调整为更快的fps
var previousFrameTime = 33;
var activeFrameTime = 33;
var animationTick = function(rafTime) {
if (scheduledHostCallback !== null) {
// 立马请求下一帧调用自己,不停的调用,队列里有很多 callback
requestAnimationFrameWithTimeout(animationTick);
} else {
// No pending work. Exit.
// 没有方法要被调度,返回
isAnimationFrameScheduled = false;
return;
}
// rafTime 是调用的当前时间,frameDeadline 为0,activeFrameTime 为 33,即保持浏览器一秒 30帧,每一帧的执行一帧完整的时间
// 用来计算下一帧可以执行的时间是多少,即nextFrameTime
var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
// 下一个方法调用进来 下一帧重新计算nextFrameTime,rafTime - frameDeadline 如果小于 0 ,说明浏览器的刷新频率要大于 30hz,帧时间是要小于 33ms的
// 如果连续两次帧的调用计算出来的时间是小于 33ms,就设置帧时间变小,
// 主要针对不同平台的刷新频率的问题,比如 vr 120帧,根据平台的刷新频率设置 activeFrameTime, 1000 / 120 -> 8
if (
nextFrameTime < activeFrameTime &&
previousFrameTime < activeFrameTime
) {
if (nextFrameTime < 8) {
// Defensive coding. We don't support higher frame rates than 120hz.
// If the calculated frame time gets lower than 8, it is probably a bug.
// 每帧最小运行时间
nextFrameTime = 8;
}
// If one frame goes long, then the next one can be short to catch up.
// If two frames are short in a row, then that's an indication that we
// actually have a higher frame rate than what we're currently optimizing.
// We adjust our heuristic dynamically accordingly. For example, if we're
// running on 120hz display or 90hz VR display.
// Take the max of the two in case one of them was an anomaly due to
// missed frame deadlines.
// 通过前后帧时间的判断,来判断平台的刷新频率,然后更新 activeFrameTime, 来达到减少React运行占用时间的目的
activeFrameTime =
nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
} else {
// 前一帧运行时间和下一帧运行时间相同
previousFrameTime = nextFrameTime;
}
// 第一个 frameDeadline
frameDeadline = rafTime + activeFrameTime;
// isMessageEventScheduled 会在 cancelHostCallback 和 idleTick 里都会设置为 false
if (!isMessageEventScheduled) {
isMessageEventScheduled = true;
// 通过 requestAnimationFrame 调用完 callback 后立马会进入浏览器动画更新的设定,往任务队列里插入react任务来模拟 requestIdleCallback 方法
// postMessage属于 macrotask,等待浏览器执行完再执行队列里的任务,即使每一帧的时间为33ms,但等到 message 接收时,实际留给react的时间没有 33ms
// 给任务队列插入 react 任务,等浏览器执行完自己的任务再执行这里队列里的
window.postMessage(messageKey, '*');
}
};
// 接受 react 任务
window.addEventListener('message', idleTick, false);
var idleTick = function(event) {
// 判断 key 和 判断 message 源
if (event.source !== window || event.data !== messageKey) {
return;
}
isMessageEventScheduled = false;
// 先赋值 再重置,
// scheduledHostCallback -> flushWork
// timeoutTime -> firstCallbackNode.expirationTime 到期时间
var prevScheduledCallback = scheduledHostCallback;
var prevTimeoutTime = timeoutTime;
scheduledHostCallback = null;
timeoutTime = -1;
var currentTime = getCurrentTime();
var didTimeout = false;
// 表示浏览器动画或用户反馈操作超过 33ms, 把这一帧的时间用完了,react 没有时间去更新了
if (frameDeadline - currentTime <= 0) {
// There's no time left in this idle period. Check if the callback has
// a timeout and whether it's been exceeded.
// 表示当前任务也已经过期
if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
// Exceeded the timeout. Invoke the callback even though there's no
// time left.
// 标识已过期,准备强制更新
didTimeout = true;
} else {
// No timeout.
// 没有过期,且 isAnimationFrameScheduled 为 false 的情况,则直接调用 requestAnimationFrameWithTimeout
// 在 animationTick 函数中 判断 scheduledHostCallback === null 时才会赋值其为false,
// 而 scheduledHostCallback === null 只有在 cancelHostCallback 函数 和 idleTick 函数中执行
if (!isAnimationFrameScheduled) {
// Schedule another animation callback so we retry later.
// 调度另一个动画回调,以便我们稍后重试
isAnimationFrameScheduled = true;
requestAnimationFrameWithTimeout(animationTick);
}
// Exit without invoking the callback.
// 没有过期,则退出而不执行callback,将scheduledHostCallback、tiemoutTime 恢复原来值,直接return
scheduledHostCallback = prevScheduledCallback;
timeoutTime = prevTimeoutTime;
return;
}
}
if (prevScheduledCallback !== null) {
// 正在调用这个 callback
isFlushingHostCallback = true;
try {
// 根据 didTimeout 判断 是否强制执行更新
prevScheduledCallback(didTimeout);
} finally {
isFlushingHostCallback = false;
}
}
};
var deadlineObject = {
timeRemaining,
didTimeout: false,
};
// 默认 hasNativePerformanceNow = true
var timeRemaining = function() {
// ...
// We assume that if we have a performance timer that the rAF callback
// gets a performance timer value. Not sure if this is always true.
// frameDeadline - now() > 0 这一帧的渲染剩余多少时间, 在 shouldYield 中使用并判断
// 模拟传给 requestIdleCallback方法的回调函数的 IdleDeadline 参数 https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline 表示当前闲置周期的预估剩余毫秒数
var remaining = getFrameDeadline() - performance.now();
return remaining > 0 ? remaining : 0;
}
// didTimout -> firstCallbackNode 是否已经超时的判断
function flushWork(didTimeout) {
// 开始调用 callback -> ensureHostCallbackIsScheduled 函数会直接return
isExecutingCallback = true;
deadlineObject.didTimeout = didTimeout;
try {
// firstCallbackNode的任务已经过期
if (didTimeout) {
// Flush all the expired callbacks without yielding.
// 循环
while (firstCallbackNode !== null) {
// Read the current time. Flush all the callbacks that expire at or
// earlier than that time. Then read the current time again and repeat.
// This optimizes for as few performance.now calls as possible.
var currentTime = getCurrentTime();
// 第一个 expirationTime 肯定小于 currentTime, 为过期任务
if (firstCallbackNode.expirationTime <= currentTime) {
// 循环执行callback链表,直到第一个没有过期的任务为止, 已经过期的任务都强制输出
do {
// flushFirstCallback 会将 firstCallbackNode.next 赋值给 firstCallbackNode
flushFirstCallback();
} while (
// firstCallbackNode 变成下一个,循环判断链表任务是否过期
firstCallbackNode !== null &&
firstCallbackNode.expirationTime <= currentTime
);
// 已经过期的任务都输出了,下一次判断if (firstCallbackNode.expirationTime <= currentTime) 为false,直接break
continue;
}
break;
}
} else {
// Keep flushing callbacks until we run out of time in the frame.
// 任务没有过期, 继续刷新回调,直到帧中的时间不足为止。
if (firstCallbackNode !== null) {
do {
flushFirstCallback();
} while (
// 帧还有时间空闲(还有剩余时间)才执行 callback
firstCallbackNode !== null &&
getFrameDeadline() - getCurrentTime() > 0
);
}
}
} finally {
isExecutingCallback = false;
if (firstCallbackNode !== null) {
// There's still work remaining. Request another callback.
// 还剩有 firsrCallbackNode 时,调用ensureHostCallbackIsScheduled ,此时 isHostCallbackScheduled 为 true,则会执行 cancelHostCallback 重置所有的调度常量,将 scheduledHostCallback重置为null,避免在 animationTick 中将老的 callback 重复执行一边
ensureHostCallbackIsScheduled();
} else {
isHostCallbackScheduled = false;
}
// Before exiting, flush all the immediate work that was scheduled.
// firstCallbackNode.priorityLevel === currentPriorityLevel 这个函数不会执行
flushImmediateWork();
}
}
function flushFirstCallback() {
var flushedNode = firstCallbackNode;
// Remove the node from the list before calling the callback. That way the
// list is in a consistent state even if the callback throws.
var next = firstCallbackNode.next;
// 表示只有一个 callbackNode
if (firstCallbackNode === next) {
// This is the last callback in the list.
firstCallbackNode = null;
next = null;
} else {
// 替换 firstCallbackNode 的操作
var lastCallbackNode = firstCallbackNode.previous;
// 将 firstCallbackNode 指向 firstCallbackNode.next
firstCallbackNode = lastCallbackNode.next = next;
next.previous = lastCallbackNode;
}
// 清空老 firstCallbackNode 的 next 和 previous
flushedNode.next = flushedNode.previous = null;
// Now it's safe to call the callback.
var callback = flushedNode.callback;
var expirationTime = flushedNode.expirationTime;
var priorityLevel = flushedNode.priorityLevel;
var previousPriorityLevel = currentPriorityLevel;
var previousExpirationTime = currentExpirationTime;
currentPriorityLevel = priorityLevel;
currentExpirationTime = expirationTime;
var continuationCallback;
try {
// 执行 callback
continuationCallback = callback(deadlineObject);
} finally {
currentPriorityLevel = previousPriorityLevel;
currentExpirationTime = previousExpirationTime;
}
// performAsyncWork 没有返回则下面不会执行
if (typeof continuationCallback === 'function') {
// ....
}
}
ReactCurrentOwner
的 currentDispatcher
属性挂载 hooks api
// ReactHooks.js
import ReactCurrentOwner from './ReactCurrentOwner';
function resolveDispatcher() {
const dispatcher = ReactCurrentOwner.currentDispatcher;
invariant(
dispatcher !== null,
'Hooks can only be called inside the body of a function component.',
);
return dispatcher;
}
// ...
function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialState, initialAction);
}
// ...
ReactCurrentOwner.currentDispatcher
是在 renderRoot
的开始时时候为赋值为 Dispatcher
,结束 workLoop
后赋值为 null
.即只有在 workLoop
阶段才会有值。// ReactFiberScheduler.js
function renderRoot(root: FiberRoot, isYieldy: boolean): void {
flushPassiveEffects();
isWorking = true;
if (enableHooks) {
ReactCurrentOwner.currentDispatcher = Dispatcher;
} else {
ReactCurrentOwner.currentDispatcher = DispatcherWithoutHooks;
}
// ....
// ...
// We're done performing work. Time to clean up.
isWorking = false;
ReactCurrentOwner.currentDispatcher = null;
// ...
}
beginWork
里执行 updateFunctionComponent、mountIndeterminateComponent、 updateForwardRef
(即对应函数式组件)的更新,会先执行 prepareToUseHooks
(和 context
类似执行 prepareToReadContext
), 在 render
完成后,执行 finishHooks
.function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
// hooks 方式写的组件是可以读取 context 的,且可以读取多个 context 的,即为什么 prepareToReadContext 会在 fiber 节点上使用链表方式 currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem; 去记录 context的原因
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setCurrentPhase('render');
nextChildren = Component(nextProps, context);
setCurrentPhase(null);
} else {
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;
}
function prepareToUseHooks(
current: Fiber | null,
workInProgress: Fiber,
nextRenderExpirationTime: ExpirationTime,
): void {
if (!enableHooks) {
return;
}
// 当前更新的优先级
renderExpirationTime = nextRenderExpirationTime;
// 当前更新的 fiber
currentlyRenderingFiber = workInProgress;
// 非首次渲染,memoizedState 记录的是 functionComponent当中第一个 hooks api 对应的 hook 对象
firstCurrentHook = current !== null ? current.memoizedState : null;
}
didScheduleRenderPhaseUpdate
表示在渲染过程中产生的 update
。即在 functionComponent
的 return
之前调用了 useState
返回的 setXXX
则这个变量被赋值为 true
,目的是让在渲染过程中产生的 update
经过 while
渲染判断在一次渲染过程中就被执行掉,而不是等到下一次更新中执行hook api
都会对应一个 hook
对象,挂载在 functionComponent
对应的 fiber
节点上的 memoizedState
属性上function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
if (!enableHooks) {
return children;
}
// This must be called after every function component to prevent hooks from
// being used in classes.
// 如果是重复渲染的情况,清空全局变量 currentHook workInProgressHook,并计数 numberOfReRenders 渲染次数,在 dispatchAction 会 判断次数如果大于 RE_RENDER_LIMIT 抛出错误
// 再执行一次 Component(props, refOrContext); 将 重复渲染在一次 更新中执行掉
while (didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
// the `renderPhaseUpdates` map. Call the component again, reusing the
// work-in-progress hooks and applying the additional updates on top. Keep
// restarting until no more updates are scheduled.
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;
// 更新中的 fiber 对象
const renderedWork: Fiber = (currentlyRenderingFiber: any);
// firstWorkInProgressHook 是 functionComponent 每一个 hooks 对应的 hook 对象。
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
// 对于useEffect 或者 useLayoutEffect 生成的 effect 链
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;
// Always set during createWorkInProgress
// isReRender = false;
// These were reset above
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
return children;
}
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),
);
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
hooks api
调用时会调用 createWorkInProgressHook
创建一个 hook
对象并赋值给全局变量 workInProgressHook
。createWorkInProgressHook
内部 hook
对象构建成链表结构function useReducer<S, A>(
reducer: (S, A) => S,
initialState: S,
initialAction: A | void | null,
): [S, Dispatch<A>] {
// 找到正在执行更新的 fiber
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
// 创建一个 hook 对象, 并通过全局变量构建 hook 链表
workInProgressHook = createWorkInProgressHook();
// 非首次渲染, queue 不为空的情况
let queue: UpdateQueue<A> | null = (workInProgressHook.queue: any);
if (queue !== null) {
// Already have a queue, so this is an update.
// 重复渲染
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
// renderPhaseUpdates 在 dispatchAction 被设置已 queque 为 key 的 map对象
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
// 取出来后 删掉 queue
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
// 重复渲染的过程中 所有update 立马执行,不进行 expirationTime 的判断
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== null);
// 更新 memoizedState
workInProgressHook.memoizedState = newState;
// Don't persist the state accumlated from the render phase updates to
// the base state unless the queue is empty.
// TODO: Not sure if this is the desired semantics, but it's what we
// do for gDSFP. I can't remember why.
if (workInProgressHook.baseUpdate === queue.last) {
workInProgressHook.baseState = newState;
}
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
}
// The last update in the entire queue
const last = queue.last;
// The last update that is part of the base state.
const baseUpdate = workInProgressHook.baseUpdate;
// Find the first unprocessed update.
let first;
if (baseUpdate !== null) {
if (last !== null) {
// For the first update, the queue is a circular linked list where
// `queue.last.next = queue.first`. Once the first update commits, and
// the `baseUpdate` is no longer empty, we can unravel the list.
last.next = null;
}
first = baseUpdate.next;
} else {
// 获取第一个 update
first = last !== null ? last.next : null;
}
if (first !== null) {
// 原来的老的值
let newState = workInProgressHook.baseState;
let newBaseState = null;
let newBaseUpdate = null;
let prevUpdate = baseUpdate;
let update = first;
let didSkip = false;
// 将 update 链循环执行完
do {
const updateExpirationTime = update.expirationTime;
// hooks 之后的expirationTime的计算大小颠倒,越大表示优先级越高
if (updateExpirationTime < renderExpirationTime) {
// 更新优先级低
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
// 只有有第一个在当前更新中更新优先级小于renderExpirationTime的(需要跳过的),那么 didSkip 会被设置为 true
if (!didSkip) {
didSkip = true;
// 记录的是这个需要被跳过的 update 的前一个 update
newBaseUpdate = prevUpdate;
// 前一个 state
newBaseState = newState;
}
// Update the remaining priority in the queue.
// remainingExpirationTime 初始值为 0
// 只要有一个被跳过,则赋值为 跳过的update的对应的 expirationTime
// 最后被设置为需要跳过的 update 里优先级最大的 expirationTime
// 最后在 finishHooks 中会被设置为 fiber 对象的 expirationTime,即尚未执行更新的 update 里优先级最高的 expirationTime
if (updateExpirationTime > remainingExpirationTime) {
remainingExpirationTime = updateExpirationTime;
}
} else {
// Process this update.
const action = update.action;
// 对于 useState 来说 action 就是更新后的值,而非 function
// 即 newState = action 赋值新值
newState = reducer(newState, action);
}
prevUpdate = update;
update = update.next;
} while (update !== null && update !== first);
// 如果没有跳过的
if (!didSkip) {
// prevUpdate 为最后一个 update 即 last
newBaseUpdate = prevUpdate;
newBaseState = newState;
}
// memoizedState 更新为新的 state
workInProgressHook.memoizedState = newState;
// 更新 baseUpdate, baseUpdate 记录的要么是 last 要么是需要跳过的update 的前一个 update
// 即在开头判断 baseUpdate 如果不为 null,则从 baseUpdate.next 开始更新
workInProgressHook.baseUpdate = newBaseUpdate;
workInProgressHook.baseState = newBaseState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [workInProgressHook.memoizedState, dispatch];
}
// 首次渲染, hook.queue 为空
// There's no existing queue, so this is the initial render.
if (reducer === basicStateReducer) {
// useState 的情况
// Special case for `useState`.
if (typeof initialState === 'function') {
initialState = initialState();
}
} else if (initialAction !== undefined && initialAction !== null) {
// useReducer 接受的第三个参数,用来计算 initialState
initialState = reducer(initialState, initialAction);
}
// 将 initialState 挂载到 hook 对象上的 memoizedState 及 baseState,即使得 functionComponent 也有读取 state 的能力
// hook 对象是更细粒度的 fiber
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
// 挂载 queue,last 用来记录 dispatchAction 函数中生成的 update 链
queue = workInProgressHook.queue = {
last: null,
dispatch: null,
};
// 生成dispatch函数
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
}
hook
对象,并构建链表结构firstWorkInProgressHook
变量在这里被赋值为第一个创建的 hook
,执行完 functionComponent
得到 children
后会走 finishHooks
,将 firstWorkInProgressHook
赋值给 fiber.memoizedState
,再将 firstWorkInProgressHook
清空function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
// 都为 null,是非重复渲染的
isReRender = false;
currentHook = firstCurrentHook;
if (currentHook === null) {
// This is a newly mounted hook
// 首次渲染,创建 hook 对象
workInProgressHook = createHook();
} else {
// Clone the current hook.
workInProgressHook = cloneHook(currentHook);
}
firstWorkInProgressHook = workInProgressHook;
} else {
// There's already a work-in-progress. Reuse it.
// firstWorkInProgressHook 不为 null,说明 finishHooks 方法中的didScheduleRenderPhaseUpdate 为true 导致 while 循环在执行,即说明在重复渲染
// 重复渲染的
isReRender = true;
currentHook = firstCurrentHook;
workInProgressHook = firstWorkInProgressHook;
}
} else {
// 非首次渲染,并未执行过其他的 hooks api,也是非重复渲染的
if (workInProgressHook.next === null) {
isReRender = false;
let hook;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
currentHook = currentHook.next;
if (currentHook === null) {
// This is a newly mounted hook
hook = createHook();
} else {
// Clone the current hook.
hook = cloneHook(currentHook);
}
}
// 如果 next 为null 那么将 hook1.next = hook2
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
} else {
// There's already a work-in-progress. Reuse it.
// next 不为 null,复用原来的
// 重复渲染
isReRender = true;
workInProgressHook = workInProgressHook.next;
currentHook = currentHook !== null ? currentHook.next : null;
}
}
return workInProgressHook;
}
// createHook
function createHook(): Hook {
return {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
}
dispatch
函数update
,并构建 update
的链表结构,将 queue.last
指向最后一个产生的 update
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
// rerendered 次数 大于 RE_RENDER_LIMIT 则报错
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
const alternate = fiber.alternate;
if (
// 只有在 return 之前调用 setXXX, 即在更新当前 functionComponent 的中过程调用了 setState ,还未执行 finishHooks。 currentlyRenderingFiber => 当前 function 对应的 Fiber
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
expirationTime: renderExpirationTime,
action,
next: null,
};
// 将重复渲染的 update 收集, 在 useReducer 中会判断 renderPhaseUpdates 是否为空,并去 queue 对应的 update
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
// 重复渲染的 update 构建链表
lastRenderPhaseUpdate.next = update;
}
} else {
// 计算 expirationTime
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
// 创建 update
const update: Update<A> = {
expirationTime,
action,
next: null,
};
// useEffect 相关
flushPassiveEffects();
// Append the update to the end of the list.
// last 是 queue 用来标识最后一个 update 的
// last.next 用来标识第一个 即 first update
// 首次为 null, queue.last = update1, update1.next = update1
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
// last 不为 null 即非首次
// first -> update1
const first = last.next;
if (first !== null) {
// Still circular.
// 新的 update 即 update2.next = update1;
update.next = first;
}
// update1.next = update2;
last.next = update;
}
// last = update2;
queue.last = update;
// 加入调度
scheduleWork(fiber, expirationTime);
}
}
useEffectImpl
不同的是前两个参数不同
fiber
的 effectTag
hook
对象的 effectTag
create
为传入的匿名函数inputs
为依赖数组useLayoutEffect
会在 commitRoot
的第三次循环 commitAllLifeCycles
=> commitLifeCycles
中被执行,执行时机相当于 componentDidMount
和 componentDidUpdate
commitAllLifeCycles
时会根据 nextEffect
是否有 Passive (passiveEffect)
赋值 rootWithPendingPassiveEffects = finishedRoot
在 执行完 commitRoot
的三次循环后会判断这个变量如果存在则将 commitPassiveEffects
加入到 Schedule_scheduleCallback
异步任务调度中useEffect
会在 commitPassiveEffects
被执行,先执行 destroy
再执行 create
。加入调度任务中是为了给浏览器更多时间渲染,js执行更少的时间,防止页面阻塞。// ReactHookEffectTags
const NoEffect = /* */ 0b00000000; // => NoHookEffect
const UnmountSnapshot = /* */ 0b00000010;
const UnmountMutation = /* */ 0b00000100;
const UnmountMutation = /* */ 0b00000100;
const MountMutation = /* */ 0b00001000;
const UnmountLayout = /* */ 0b00010000;
const MountLayout = /* */ 0b00100000;
const MountPassive = /* */ 0b01000000;
const UnmountPassive = /* */ 0b10000000;
// ReactSideEffectTags
const Update = /* */ 0b000000000100; // => updateEffect
const Passive = /* */ 0b001000000000; // => passiveEffect
function useLayoutEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(
UpdateEffect,
UnmountMutation | MountLayout, // => 0b00100100
create,
inputs
);
}
export function useEffect(
create: () => mixed,
inputs: Array<mixed> | void | null,
): void {
useEffectImpl(
UpdateEffect | PassiveEffect, // => 0b001000000100
UnmountPassive | MountPassive, // => 0b11000000
create,
inputs,
);
}
fiber
标记对应的 effectTag
pushEffect
创建 hook
对象的 effect
并标记对应的 hookEffectTag
,构建 effect
链赋值给 fiber
对象的 updateQueue
areHookInputsEqual
方法对比依赖数组,如果依赖变化则执行 pushEffect
。即如果不传依赖数组,每次都会执行 pushEffect
function useEffectImpl(fiberEffectTag, hookEffectTag, create, inputs): void {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
// inputs 为传入的依赖数组,如果为空,则是回调函数自身
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
let destroy = null;
// currentHook !== null 非首次渲染
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
// 前后对比依赖项是否变化
if (areHookInputsEqual(nextInputs, prevEffect.inputs)) {
// 如果相同
pushEffect(NoHookEffect, create, destroy, nextInputs);
return;
}
}
// 第一次渲染 或者 依赖不相同
// 标记 fiber 的 effectTag 为 fiberEffectTag
currentlyRenderingFiber.effectTag |= fiberEffectTag;
workInProgressHook.memoizedState = pushEffect(
hookEffectTag,
create,
destroy,
nextInputs,
);
}
true
否则返回 false
Polyfill
function areHookInputsEqual(arr1: any[], arr2: any[]) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
for (let i = 0; i < arr1.length; i++) {
// Inlined Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
const val1 = arr1[i];
const val2 = arr2[i];
if (
(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
(val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
) {
continue;
}
return false;
}
return true;
}
functionComponent
所有的 effect
链,保存在 fiber
对象的 componentUpdateQueue
属性的 lastEffect
上finishHooks
时将 componentUpdateQueue
赋值给 fiber.updateQueue
function pushEffect(tag, create, destroy, inputs) {
const effect: Effect = {
tag, // => hookEffectTag
create,
destroy,
inputs,
// Circular
next: (null: any),
};
// 构建 functionComponent 所有的 effect 链
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
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 createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
lastEffect: null,
};
}
commitRoot
的三次循环中均有被调用,三次循环后 还有一次调用是针对 useEffect
的effect
的 destroy
,即官方推荐不能在 hooks Api
之前写 if
条件判断是否使用 hooks
,这样会下一次更新时构建的 effect
链的顺序是和上次不一样的,导致在 commitRoot
时顺序错乱。function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
if (!enableHooks) {
return;
}
// 读取 updateQueue
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
// 遍历在 useEffectImpl 中 pushEffect 生成的 effect 链 (hook对象的)
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
// 如果 effect.tag 有 unmountTag 则执行 destroy()
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = null;
if (destroy !== null) {
destroy();
}
}
// 如果 effect.tag 有 moountTag 则执行 create()
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
let destroy = create();
if (typeof destroy !== 'function') {
// 非函数返回的提示
if (__DEV__) {
if (destroy !== null && destroy !== undefined) {
warningWithoutStack(
false,
'useEffect function must return a cleanup function or ' +
'nothing.%s%s',
typeof destroy.then === 'function'
? '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
'Instead, you may write an async function separately ' +
'and then call it from inside the effect:\n\n' +
'async function fetchComment(commentId) {\n' +
' // You can await here\n' +
'}\n\n' +
'useEffect(() => {\n' +
' fetchComment(commentId);\n' +
'}, [commentId]);\n\n' +
'In the future, React will provide a more idiomatic solution for data fetching ' +
"that doesn't involve writing effects manually."
: '',
getStackByFiberInDevAndProd(finishedWork),
);
}
}
destroy = null;
}
effect.destroy = destroy;
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
unmountTag -> UnmountSnapshot
。 在 useEffectImpl
没有推入 UnmountSnapshot。
因此不会执行 destroy
mountTag -> NoHookEffect
。 也不会执行 create
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
return;
}
//...
}
//...
}
commitAllHostEffects
对 dom
节点的 PlacementAndUpdate
和 Update
会先执行 commitWork
unmountTag -> UnmountMutation
对应 useLayoutEffect
传入 UnmountMutation | MountLayout
,因此会执行 useLayoutEffect
的 destroy
mountTag -> MountMutation
。没有标记 MountMutation
因此都不会执行 create
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
// ...
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// Note: We currently never use MountMutation, but useLayout uses
// UnmountMutation.
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
// ...
}
}
commitAllLifeCycles -> commitLifeCycles
unmountTag -> UnmountLayout
. 都没有标记 UnmountLayout
都不会执行 destroy
mountTag -> MountLayout
. useLayoutEffect
标记了 MountLayout
因此 会执行它的 create
方法,对应 classComponent
组件会在这里执行 componentDidMount
和 componentDidUpdate
commitLifeCycles
之后,如果 effectTag
有 Passive
则赋值 rootWithPendingPassiveEffects = finishedRoot
。为 useEffect
执行做准备function commitAllLifeCycles(
finishedRoot: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
if (__DEV__) {
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
ReactStrictModeWarnings.flushLegacyContextWarning();
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.flushPendingDeprecationWarnings();
}
}
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
recordEffect();
const current = nextEffect.alternate;
commitLifeCycles(
finishedRoot,
current,
nextEffect,
committedExpirationTime,
);
}
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
// useEffect 中标记 fiber 的 effectTag 为 updateEffect | passiveEffect
// 因此对于 useEffect 的节点会满足条件,从而赋值 rootWithPendingPassiveEffects
if (enableHooks && effectTag & Passive) {
rootWithPendingPassiveEffects = finishedRoot;
}
nextEffect = nextEffect.nextEffect;
}
}
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
break;
}
// ...
}
}
commitRoot
执行完三次循环后,如果 rootWithPendingPassiveEffects
不为空则将 commitPassiveEffects
加入到 scheduleWork
的调度队列中useEffect
执行是个异步过程,要等到所有节点更新完成、浏览器重新渲染 dom
后才会执行 commitPassiveEffects
。即 useLayoutEffect
中可以同步的获取更新后的 dom
.// commitRoot
//...
if (
enableHooks &&
firstEffect !== null &&
rootWithPendingPassiveEffects !== null
) {
// This commit included a passive effect. These do not need to fire until
// after the next paint. Schedule an callback to fire them in an async
// event. To ensure serial execution, the callback will be flushed early if
// we enter rootWithPendingPassiveEffects commit phase before then.
let callback = commitPassiveEffects.bind(null, root, firstEffect);
if (enableSchedulerTracing) {
// TODO: Avoid this extra callback by mutating the tracing ref directly,
// like we do at the beginning of commitRoot. I've opted not to do that
// here because that code is still in flux.
callback = Schedule_tracing_wrap(callback);
}
passiveEffectCallbackHandle = Schedule_scheduleCallback(callback);
passiveEffectCallback = callback;
}
//...
effect
找到标记了 Passive
的执行 commitPassiveHookEffects
function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
rootWithPendingPassiveEffects = null;
passiveEffectCallbackHandle = null;
passiveEffectCallback = null;
// Set this to true to prevent re-entrancy
const previousIsRendering = isRendering;
isRendering = true;
let effect = firstEffect;
do {
if (effect.effectTag & Passive) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitPassiveHookEffects, null, effect);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
commitPassiveHookEffects(effect);
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
captureCommitPhaseError(effect, error);
}
}
effect = effect.nextEffect;
} while (effect !== null);
isRendering = previousIsRendering;
// Check if work was scheduled by one of the effects
const rootExpirationTime = root.expirationTime;
if (rootExpirationTime !== NoWork) {
requestWork(root, rootExpirationTime);
}
}
unmountTag -> UnmountPassive
, mountTag -> NoHookEffect
因此只执行 useEffect
的 destroy
unmountTag -> NoHookEffect
, 只执行 useEffect
的 create
function commitPassiveHookEffects(finishedWork: Fiber): void {
commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork);
commitHookEffectList(NoHookEffect, MountPassive, finishedWork);
}
readContext
获取 context
function useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T {
// Ensure we're in a function component (class components support only the
// .unstable_read() form)
resolveCurrentlyRenderingFiber();
return readContext(context, observedBits);
}
ref
对象挂载到 hook.memoizedState
属性上。function useRef<T>(initialValue: T): {current: T} {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let ref;
if (workInProgressHook.memoizedState === null) {
// ref 对象
ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
} else {
ref = workInProgressHook.memoizedState;
}
return ref;
}
forwardRef
往父组件传递方法或者属性useLayoutEffect
function useImperativeMethods<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
inputs: Array<mixed> | void | null,
): void {
// TODO: If inputs are provided, should we skip comparing the ref itself?
const nextInputs =
inputs !== null && inputs !== undefined
? inputs.concat([ref])
: [ref, create];
// TODO: I've implemented this on top of useEffect because it's almost the
// same thing, and it would require an equal amount of code. It doesn't seem
// like a common enough use case to justify the additional size.
useLayoutEffect(() => {
// 如果传递下来的 ref 是个函数
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
// 将 create 方法执行结果当作参数
refCallback(inst);
return () => refCallback(null);
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
// 直接将 create 方法的结果挂载到传递下来的 ref.current 上
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}, nextInputs);
}
callback
function useCallback<T>(
callback: T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [callback];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
workInProgressHook.memoizedState = [callback, nextInputs];
return callback;
}
nextCreate
的执行结果,如果依赖项相同,则会返回上一次的执行结果function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate();
workInProgressHook.memoizedState = [nextValue, nextInputs];
return nextValue;
}
renderRoot
最后会调用 onComplete
方法设置 root.finishedWork
(即 RootFiber
) 和 root.pendingCommitExpirationTime
// renderRoot
// finishedWork 即 RootFiber 对象
const rootWorkInProgress = root.current.alternate;
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
// onComplete
function onComplete(
root: FiberRoot,
finishedWork: Fiber,
expirationTime: ExpirationTime,
) {
root.pendingCommitExpirationTime = expirationTime;
root.finishedWork = finishedWork;
}
performWorkOnRoot
如果 finishedWork
不为 null
,则调用 completeRoot -> commitRoot
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isExpired: boolean,
) {
// ....
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
// ...
}
// completeRoot
function completeRoot(
root: FiberRoot,
finishedWork: Fiber,
expirationTime: ExpirationTime,
): void {
// ...
commitRoot(root, finishedWork);
}
commit
阶段不能被中断,尽量少的任务交给 commit
阶段
三次循环,都是对 firstEffect
到 lastEffect
单向链上根据不同 effectTag
进行不同的更新
function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
// renderRoot 中也是 true, 表示正在进行更新操作
isWorking = true;
// 标记提交阶段
isCommitting = true;
startCommitTimer();
// 保存当前正在更新的 expirationTime
const committedExpirationTime = root.pendingCommitExpirationTime;
root.pendingCommitExpirationTime = NoWork;
// Update the pending priority levels to account for the work that we are
// about to commit. This needs to happen before calling the lifecycles, since
// they may schedule additional updates.
// 自己的 expirationTime,
const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
// 所有子树中优先级最高的任务
const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
// 找到这两个当中最小且非 NoWork 的 expirationTime
const earliestRemainingTimeBeforeCommit =
updateExpirationTimeBeforeCommit === NoWork ||
(childExpirationTimeBeforeCommit !== NoWork &&
childExpirationTimeBeforeCommit < updateExpirationTimeBeforeCommit)
? childExpirationTimeBeforeCommit
: updateExpirationTimeBeforeCommit;
// 标记优先级
markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
let firstEffect;
//
if (finishedWork.effectTag > PerformedWork) {
// A fiber's effect list consists only of its children, not itself. So if
// the root has an effect, we need to add it to the end of the list. The
// resulting list is the set that would belong to the root's parent, if
// it had one; that is, all the effects in the tree including the root.
if (finishedWork.lastEffect !== null) {
// 自身的如果有 effect 且 lastEffect 不为 null,需要加入到单链表的最后
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// There is no effect on the root.
// 没有 effect 就用第一个effect
firstEffect = finishedWork.firstEffect;
}
// ...三次循环
prepareForCommit(root.containerInfo);
// Invoke instances of getSnapshotBeforeUpdate before mutation.
// 每次循环完都需要重新赋值全局变量 nextEffect = firstEffect ,即从头到尾开始走三次循环
nextEffect = firstEffect;
startCommitSnapshotEffectsTimer();
// 第一次循环, 调用 classComponent 可能存在的 getSnapshotBeforeUpdate 生命周期方法
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
commitBeforeMutationLifecycles();
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitSnapshotEffectsTimer();
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this batch.
// This enables them to be grouped later.
recordCommitTime();
}
// Commit all the side-effects within a tree. We'll do this in two passes.
// The first pass performs all the host insertions, updates, deletions and
// ref unmounts.
nextEffect = firstEffect;
startCommitHostEffectsTimer();
// 第二次循环,操作 dom 节点的插入,删除,更新操作
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(null, commitAllHostEffects, null);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 操作 dom 节点的插入、删除、更新操作
commitAllHostEffects();
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
// Clean-up
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
stopCommitHostEffectsTimer();
resetAfterCommit(root.containerInfo);
// The work-in-progress tree is now the current tree. This must come after
// the first pass of the commit phase, so that the previous tree is still
// current during componentWillUnmount, but before the second pass, so that
// the finished work is current during componentDidMount/Update.
root.current = finishedWork;
// In the second pass we'll perform all life-cycles and ref callbacks.
// Life-cycles happen as a separate pass so that all placements, updates,
// and deletions in the entire tree have already been invoked.
// This pass also triggers any renderer-specific initial effects.
nextEffect = firstEffect;
startCommitLifeCyclesTimer();
// 第三次循环,组件的生命周期方法调用
while (nextEffect !== null) {
let didError = false;
let error;
if (__DEV__) {
invokeGuardedCallback(
null,
commitAllLifeCycles,
null,
root,
committedExpirationTime,
);
if (hasCaughtError()) {
didError = true;
error = clearCaughtError();
}
} else {
try {
// 组件生命周期调用
commitAllLifeCycles(root, committedExpirationTime);
} catch (e) {
didError = true;
error = e;
}
}
if (didError) {
invariant(
nextEffect !== null,
'Should have next effect. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
captureCommitPhaseError(nextEffect, error);
if (nextEffect !== null) {
nextEffect = nextEffect.nextEffect;
}
}
}
// 全局遍量 reset
isCommitting = false;
isWorking = false;
stopCommitLifeCyclesTimer();
stopCommitTimer();
onCommitRoot(finishedWork.stateNode);
// 在执行 classComponent 的生命周期方法时又可能产生新的更新,childExpirationTime 可能会改变
const updateExpirationTimeAfterCommit = finishedWork.expirationTime;
const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;
const earliestRemainingTimeAfterCommit =
updateExpirationTimeAfterCommit === NoWork ||
(childExpirationTimeAfterCommit !== NoWork &&
childExpirationTimeAfterCommit < updateExpirationTimeAfterCommit)
? childExpirationTimeAfterCommit
: updateExpirationTimeAfterCommit;
if (earliestRemainingTimeAfterCommit === NoWork) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}
// 设置 root.expirationTime 为新计算出来的上面两个值中 小的并且非 NoWork 的 expirationTime
onCommit(root, earliestRemainingTimeAfterCommit);
}
classComponent
可能存在的 getSnapshotBeforeUpdate
生命周期方法classComponent
才会调用 getSnapshotBeforeUpdate
instance.__reactInternalSnapshotBeforeUpdate
上,在 componentDidUpdate
里会接受这个参数// ReactFiberScheduler.js 中的 commitBeforeMutationLifecycles 方法
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
if (__DEV__) {
ReactCurrentFiber.setCurrentFiber(nextEffect);
}
const effectTag = nextEffect.effectTag;
// 如果有 snapshot effect,在 beginWork 中更新 classComponent -> updateInstance 中如果组件实例有 getSnapshotBeforeUpdate 则会标记 snapshot
if (effectTag & Snapshot) {
recordEffect();
const current = nextEffect.alternate; // workInProgress
// 这里调用的是 ReactFiberCommitWork.js 中的同名方法
commitBeforeMutationLifeCycles(current, nextEffect);
}
// Don't cleanup effects yet;
// This will be done by commitAllLifeCycles()
nextEffect = nextEffect.nextEffect;
}
}
// 只有 classComponent 才会调用 getSnapshotBeforeUpdate
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
// 非首次渲染
if (current !== null) {
// 老 props
const prevProps = current.memoizedProps;
// 老 state
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
// 组件实例
const instance = finishedWork.stateNode;
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
// 获取快照, getSnapshotBeforeUpdate 方法里可以调用 this
const snapshot = instance.getSnapshotBeforeUpdate(
prevProps,
prevState,
);
if (__DEV__) {}
// 获取的快照挂载在 instance 上,componentDidUpdate 方法的第三个参数即 snapshot
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
stopPhaseTimer();
}
}
return;
}
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
default: {}
}
}
dom
节点的插入、删除、更新操作ref
function commitAllHostEffects() {
while (nextEffect !== null) {
recordEffect();
const effectTag = nextEffect.effectTag;
// 判断内部是否为文字节点
if (effectTag & ContentReset) {
// 文字节点设置为空字符串
commitResetTextContent(nextEffect);
}
// ref操作
if (effectTag & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
}
// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every
// possible bitmap value, we remove the secondary effects from the
// effect tag and switch on that value.
// 插入、更新、删除的 effect
let primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
// 新增的,插入
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted
// does and isMounted is deprecated anyway so we should be able
// to kill this.
// 执行插入操作后,要去掉 effectTag
nextEffect.effectTag &= ~Placement;
break;
}
// 插入且更新
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is inserted, before
// any life-cycles like componentDidMount gets called.
// 执行插入操作后,要去掉 effectTag
nextEffect.effectTag &= ~Placement;
// Update
// 节点之前存在,有新的 content 或者 attribute 要更新 ,执行 commitWork
// 只会更新 HostComponent, HostText
// nextEffect 为新的
// current 为老的
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 更新
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
commitDeletion(nextEffect);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
function commitResetTextContent(current: Fiber) {
if (!supportsMutation) {
return;
}
resetTextContent(current.stateNode);
}
// resetTextContent
function resetTextContent(domElement: Instance): void {
setTextContent(domElement, '');
}
// setTextContent 设置文本内容
setTextContent = function(node: Element, text: string): void {
if (text) {
let firstChild = node.firstChild;
if (
firstChild &&
firstChild === node.lastChild &&
firstChild.nodeType === TEXT_NODE
) {
firstChild.nodeValue = text;
return;
}
}
node.textContent = text;
};
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
if (typeof currentRef === 'function') {
currentRef(null);
} else {
currentRef.current = null;
}
}
}
insertBefore
方法需要找父节点,getHostParentFiber
即往上找到第一个节点类型是 原生 dom 节点
、Root 节点
、portal
的父节点getHostSibling
方法用来找插入的节点的参考节点 parent.insertBefore(newNode, beforeNode)
, 如果有 beforeNode
, 则调用 insert
插入,否则是 appendChild
function commitPlacement(finishedWork: Fiber): void {
// ReactDOM 调用 supportsMutation 默认为 true
if (!supportsMutation) {
return;
}
// Recursively insert all host nodes into the parent.
// 往上找到第一个节点类型是 原生dom节点、Root 节点、portal的父节点
const parentFiber = getHostParentFiber(finishedWork);
// Note: these two variables *must* always be updated together.
let parent;
// 是否为容器型
let isContainer;
switch (parentFiber.tag) {
case HostComponent:
parent = parentFiber.stateNode;
isContainer = false;
break;
case HostRoot:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentFiber.stateNode.containerInfo;
isContainer = true;
break;
default:
invariant(
false,
'Invalid host parent fiber. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
if (parentFiber.effectTag & ContentReset) {
// Reset the text content of the parent before doing any insertions
// 清除父节点的文本节点
resetTextContent(parent);
// Clear ContentReset from the effect tag
parentFiber.effectTag &= ~ContentReset;
}
// 找需要使用 insertBefore 来插入的节点 , parent.insertBefore(newNode, beforeNode)
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = finishedWork;
while (true) {
// 原生 dom 节点 或者 文本节点
if (node.tag === HostComponent || node.tag === HostText) {
// insertBefore 插入
if (before) {
if (isContainer) {
insertInContainerBefore(parent, node.stateNode, before);
} else {
insertBefore(parent, node.stateNode, before);
}
} else {
// appendChild
if (isContainer) {
appendChildToContainer(parent, node.stateNode);
} else {
appendChild(parent, node.stateNode);
}
}
} else if (node.tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 这里和 completeWork 里的 appendAllChildren 类似
// 即节点不是 dom 节点(文本节点), 并且 node.child 不为 null,应该要遍历其子节点,其实就是 classComponent
node.child.return = node;
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
// 深度优先遍历
// 这个小循环找有兄弟节点的父节点,如果 sibling 不为 null 则会去遍历兄弟节点
// 如果没有兄弟节点,继续往上找,直到没有父节点 或者 父节点为当前遍历的节点时停止循环
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
// 继续遍历兄弟节点
node = node.sibling;
}
}
原生 dom 节点
、Root 节点
、portal
的父节点function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
if (isHostParent(parent)) {
return parent;
}
parent = parent.return;
}
}
function isHostParent(fiber: Fiber): boolean {
return (
fiber.tag === HostComponent ||
fiber.tag === HostRoot ||
fiber.tag === HostPortal
);
}
HostComponent
或者 HostText
的兄弟节点dom 节点
的父节点,或者从父节点里的兄弟节点里找第一个 dom 节点effectTag
不是 placement
才返回function getHostSibling(fiber: Fiber): ?Instance {
// We're going to search forward into the tree until we find a sibling host
// node. Unfortunately, if multiple insertions are done in a row we have to
// search past them. This leads to exponential search for the next sibling.
// TODO: Find a more efficient way to do this.
// fiber 是待插入(insertBefore 或者 appendChild )的那个节点,如果要使用 insertBefore 就要找到它的第一个原生 dom 节点的兄弟节点
let node: Fiber = fiber;
siblings: while (true) {
// If we didn't find anything, let's try the next sibling.
// 如果没有兄弟节点,则一直往上找父节点,直到找到父节点是 HostComponent、HostRoot、HostPortal这三种或者为null为止,即确定父节点肯定是原生 dom 节点
// 或者从父节点的兄弟节点里找 dom 节点
while (node.sibling === null) {
if (node.return === null || isHostParent(node.return)) {
// If we pop out of the root or hit the parent the fiber we are the
// last sibling.
return null;
}
node = node.return;
}
// 有兄弟节点的情况
node.sibling.return = node.return;
// 从兄弟节点开始找第一个原生 dom 节点
node = node.sibling;
// 如果不是原生 dom 节点 或者 text,那么就要去找它(classComponent)的 child
while (node.tag !== HostComponent && node.tag !== HostText) {
// If it is not host node and, we might have a host node inside it.
// Try to search down until we find one.
// 如果这个节点也是标记插入,那么要跳过
if (node.effectTag & Placement) {
// If we don't have a child, try the siblings instead.
continue siblings;
}
// If we don't have a child, try the siblings instead.
// We also skip portals because they are not part of this host tree.
// 没有 child 或者是 portal 跳过
if (node.child === null || node.tag === HostPortal) {
continue siblings;
} else {
// 从 child 里找第一个 dom 节点
node.child.return = node;
node = node.child;
}
}
// Check if this host node is stable or about to be placed.
// 到这 node 肯定是原生 dom 节点了,并且是要没有标记插入的才算真正找到
if (!(node.effectTag & Placement)) {
// Found it!
return node.stateNode;
}
}
}
function insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
}
function appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
commitPlacement
后要去掉 placement
的 effectTag
content
或者 attribute
要更新时,执行 commitWork
HostComponent,HostText
// current 是老的节点
// newEffect 是新的节点
const current = nextEffect.alternate;
commitWork(current, nextEffect);
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
if (!supportsMutation) {
commitContainer(finishedWork);
return;
}
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// 节点已经创建
if (instance != null) {
// Commit the work prepared earlier.
// 新节点上的 props
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
// 老节点上的 props
const oldProps = current !== null ? current.memoizedProps : newProps;
// dom 标签
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
// updatePayload 为 diffProperties 是创建的 [k1, v1, k2, v2] 格式的需要更新的 prop 的数组
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
// 文本节点直接设置 textInstance.nodeValue = newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
return;
}
case IncompleteClassComponent: {
return;
}
default: {
}
}
}
dom
上挂载的 props
property
function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
updatePayload
上需要更新的属性更新input radio
标签单独处理,更新 checked
function updateProperties(
domElement: Element,
updatePayload: Array<any>,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
): void {
// Update checked *before* name.
// In the middle of an update, it is possible to have multiple checked.
// When a checked radio tries to change name, browser makes another radio's checked false.
if (
tag === 'input' &&
nextRawProps.type === 'radio' &&
nextRawProps.name != null
) {
ReactDOMInput.updateChecked(domElement, nextRawProps);
}
const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
// Apply the diff.
// 更新 property
updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag,
);
// TODO: Ensure that an update gets scheduled if any of the special props
// changed.
// 对可交互的标签单独处理
switch (tag) {
case 'input':
// Update the wrapper around inputs *after* updating props. This has to
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
// raise warnings and prevent the new value from being assigned.
ReactDOMInput.updateWrapper(domElement, nextRawProps);
break;
case 'textarea':
ReactDOMTextarea.updateWrapper(domElement, nextRawProps);
break;
case 'select':
// <select> value update needs to occur after <option> children
// reconciliation
ReactDOMSelect.postUpdateWrapper(domElement, nextRawProps);
break;
}
}
updatePayload [k1, v1, k2, v2]
遍历更新// 和 completeWork 中 finalizeInitialChildren 初始化属性做的事相似
function updateDOMProperties(
domElement: Element,
updatePayload: Array<any>,
wasCustomComponentTag: boolean,
isCustomComponentTag: boolean,
): void {
// TODO: Handle wasCustomComponentTag
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === STYLE) {
// 设置 style 属性,
CSSPropertyOperations.setValueForStyles(domElement, propValue);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 设置 html
setInnerHTML(domElement, propValue);
} else if (propKey === CHILDREN) {
// 设置 textContent
setTextContent(domElement, propValue);
} else {
// 设置 dom 属性
DOMPropertyOperations.setValueForProperty(
domElement,
propKey,
propValue,
isCustomComponentTag,
);
}
}
}
ref
classComponent
执行 componentWillUnmount
方法fiber
上的相关属性function commitDeletion(current: Fiber): void {
if (supportsMutation) {
// Recursively delete all host nodes from the parent.
// Detach refs and call componentWillUnmount() on the whole subtree.
// ReactDOM 只会走这个方法
unmountHostComponents(current);
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitNestedUnmounts(current);
}
// 清空 Fiber 上的属性
detachFiber(current);
}
function unmountHostComponents(current): void {
// We only have the top Fiber that was deleted but we need recurse down its
// children to find all the terminal nodes.
let node: Fiber = current;
// Each iteration, currentParent is populated with node's host parent if not
// currentParentIsValid.
let currentParentIsValid = false;
// Note: these two variables *must* always be updated together.
let currentParent;
let currentParentIsContainer;
while (true) {
if (!currentParentIsValid) {
// 找到 parent 是 HostComponent、HostRoot、HostPortal 类型的节点赋值给 currentParent
let parent = node.return;
findParent: while (true) {
switch (parent.tag) {
case HostComponent:
currentParent = parent.stateNode;
currentParentIsContainer = false;
break findParent;
case HostRoot:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
case HostPortal:
currentParent = parent.stateNode.containerInfo;
currentParentIsContainer = true;
break findParent;
}
parent = parent.return;
}
currentParentIsValid = true;
}
if (node.tag === HostComponent || node.tag === HostText) {
// 原生 dom 节点的子节点下 也有 classComponent 或者 portal,因此要递归调用遍历整个树的每个节点
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
// 如果是原生 dom 节点,直接 removeChild 删除
if (currentParentIsContainer) {
removeChildFromContainer((currentParent: any), node.stateNode);
} else {
removeChild((currentParent: any), node.stateNode);
}
// Don't visit children because we already visited them.
} else if (node.tag === HostPortal) {
// When we go into a portal, it becomes the parent to remove from.
// We will reassign it back when we pop the portal on the way up.
// 如果是 HostPortal 继续循环子节点
currentParent = node.stateNode.containerInfo;
currentParentIsContainer = true;
// Visit children because portals might contain host components.
if (node.child !== null) {
// 子节点不为 null 继续往下遍历
node.child.return = node;
node = node.child;
continue;
}
} else {
// 非 dom 节点 和非 portal ,即 classComponent
commitUnmount(node);
// Visit children because we may find more host components below.
if (node.child !== null) {
// 子节点不为 null 继续往下遍历
node.child.return = node;
node = node.child;
continue;
}
}
if (node === current) {
return;
}
// 深度遍历,往上找 sibling
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return;
if (node.tag === HostPortal) {
// When we go out of the portal, we need to restore the parent.
// Since we don't keep a stack of them, we will search for it.
currentParentIsValid = false;
}
}
node.sibling.return = node.return;
node = node.sibling;
}
}
ref
componentWillUnmount
portal
会递归调用 unmountHostComponents
function commitUnmount(current: Fiber): void {
onCommitUnmount(current);
switch (current.tag) {
case ClassComponent: {
// 卸载 ref
safelyDetachRef(current);
const instance = current.stateNode;
// 执行 componentWillUnmount
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
// 卸载 ref
safelyDetachRef(current);
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
if (supportsMutation) {
// 递归调用 unmountHostComponents
unmountHostComponents(current);
} else if (supportsPersistence) {
emptyPortalContainer(current);
}
return;
}
}
}
// safelyDetachRef
function safelyDetachRef(current: Fiber) {
const ref = current.ref;
if (ref !== null) {
if (typeof ref === 'function') {
if (__DEV__)
} else {
try {
ref(null);
} catch (refError) {
captureCommitPhaseError(current, refError);
}
}
} else {
ref.current = null;
}
}
}
HostComponent
执行卸载,原生 dom 节点
的子节点下也有 classComponent
或者 portal
HostPortal
的目的function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
while (true) {
// 如果 node 是 portal (只会发生在 portal 为 原生 dom 节点的 sibling 的情况,如果要卸载的组件的第一个 child 就是 portal,那么就会在 unmountHostComponents 里循环卸载子节点了),-> commitUnmount -> unmountHostComponents -> portal 会去遍历 child -> child 为 hostComponent -> commitNestedUnmounts -> commitUnmount 卸载 child
// commitUnmount 卸载 child -> portal 会执行 removeChildFromContainer -> 最后依次返回
// 嵌套递归调用卸载节点,达到卸载 HostPortal 的目的
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (
node.child !== null &&
// If we use mutation we drill down into portals using commitUnmount above.
// If we don't use mutation we drill down into portals here instead.
(!supportsMutation || node.tag !== HostPortal)
) {
// 不是 portal 并且有 child,继续循环
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
// 深度遍历,往上找 sibling
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.removeChild(child);
}
function removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
}
componentDidMount
, 更新渲染执行 componentDidUpdate
setState
的 callback
回调函数, (componentDidCatch
也是在 callback
中执行)effectTag
为 Update
或者 Callback
则去调用 commitLifeCycles
function commitAllLifeCycles(
finishedRoot: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
recordEffect();
// current 为老的
const current = nextEffect.alternate;
commitLifeCycles(
finishedRoot,
current,
nextEffect,
committedExpirationTime,
);
}
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
const next = nextEffect.nextEffect;
// Ensure that we clean these up so that we don't accidentally keep them.
// I'm not actually sure this matters because we can't reset firstEffect
// and lastEffect since they're on every node, not just the effectful
// ones. So we have to clean everything as we reuse nodes anyway.
nextEffect.nextEffect = null;
// Ensure that we reset the effectTag here so that we can rely on effect
// tags to reason about the current life-cycle.
nextEffect = next;
}
}
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedExpirationTime: ExpirationTime,
): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
// 首次渲染
if (current === null) {
startPhaseTimer(finishedWork, 'componentDidMount');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
stopPhaseTimer();
} else {
// 更新渲染
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
startPhaseTimer(finishedWork, 'componentDidUpdate');
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(
prevProps, // setState 后产生的新的 props
prevState, // setState 后产生的新的 state
instance.__reactInternalSnapshotBeforeUpdate, // Snapshot 快照
);
stopPhaseTimer();
}
}
const updateQueue = finishedWork.updateQueue;
// updateQueue 不为空,要执行 queue 里可能存在的 callback
if (updateQueue !== null) {
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
case HostRoot: {
// Root 节点, ReactDOM.render 方法会创建一个update,这个方法可以接受的第三个参数就是 callback,在第一次渲染结束后调用
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
let instance = null;
if (finishedWork.child !== null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
// 首次渲染且 effecttag 为 update,则会自动 focus 需要 autofocus 的节点
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
// Button, Input, Select, Textarea 标签,props.autofocus 为真,则首次渲染会 focus
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
case Profiler: {
}
case SuspenseComponent: {
if (finishedWork.effectTag & Callback) {
// In non-strict mode, a suspense boundary times out by commiting
// twice: first, by committing the children in an inconsistent state,
// then hiding them and showing the fallback children in a subsequent
// commit.
const newState: SuspenseState = {
alreadyCaptured: true,
didTimeout: false,
timedOutAt: NoWork,
};
finishedWork.memoizedState = newState;
scheduleWork(finishedWork, Sync);
return;
}
let oldState: SuspenseState | null =
current !== null ? current.memoizedState : null;
let newState: SuspenseState | null = finishedWork.memoizedState;
let oldDidTimeout = oldState !== null ? oldState.didTimeout : false;
let newDidTimeout;
let primaryChildParent = finishedWork;
if (newState === null) {
newDidTimeout = false;
} else {
newDidTimeout = newState.didTimeout;
if (newDidTimeout) {
primaryChildParent = finishedWork.child;
newState.alreadyCaptured = false;
if (newState.timedOutAt === NoWork) {
// If the children had not already timed out, record the time.
// This is used to compute the elapsed time during subsequent
// attempts to render the children.
newState.timedOutAt = requestCurrentTime();
}
}
}
if (newDidTimeout !== oldDidTimeout && primaryChildParent !== null) {
hideOrUnhideAllChildren(primaryChildParent, newDidTimeout);
}
return;
}
case IncompleteClassComponent:
break;
default: {}
}
}
commitUpdateEffects
调用 setStatte
的回调函数function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
// If the finished render included captured updates, and there are still
// lower priority updates left over, we need to keep the captured updates
// in the queue so that they are rebased and not dropped once we process the
// queue again at the lower priority.
// 如果有捕获错误的更新,即一次渲染过程中还没有完成的更新
if (finishedQueue.firstCapturedUpdate !== null) {
// Join the captured update list to the end of the normal list.
if (finishedQueue.lastUpdate !== null) {
// 如果还有普通的更新未完成(即低优先级的任务),将错误的更新加入到链表之后,将错误捕获的更新放到低优先级的任务上渲染
finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
}
// Clear the list of captured updates.
// 如果没有低优先级任务,则清空本次渲染过程产生的捕获错误更新
finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
}
// Commit the effects
// 调用 effect.callback, 一般是 setState 的回调函数
commitUpdateEffects(finishedQueue.firstEffect, instance);
finishedQueue.firstEffect = finishedQueue.lastEffect = null;
commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}
function commitUpdateEffects<State>(
effect: Update<State> | null,
instance: any,
): void {
while (effect !== null) {
const callback = effect.callback;
if (callback !== null) {
effect.callback = null;
// 执行 callback
callCallback(callback, instance);
}
effect = effect.nextEffect;
}
}
performWork
minExpirationTime
为 NoWork 0
,通过判断 dl.didTimeout
是否过期,如果过期则设置 root.newExpirationTimeToWorkOn
为当前时间,表示这个任务可以直接执行,不需要判断是否超过帧时间minExpirationTime
为 Sync 1
function performAsyncWork(dl) {
// didTimeout 为 true 任务过期,立即执行
if (dl.didTimeout) {
if (firstScheduledRoot !== null) {
// 设置当前渲染时间为当前时间
recomputeCurrentRendererTime();
let root: FiberRoot = firstScheduledRoot;
do {
// 标记 root 节点变量, 如果当前任务过期设置root.newExpirationTimeToWorkOn 为当前时间
didExpireAtExpirationTime(root, currentRendererTime);
// 找 下一个 root
root = (root.nextScheduledRoot: any);
} while (root !== firstScheduledRoot);
}
}
performWork(NoWork, dl);
}
function performSyncWork() {
performWork(Sync, null);
}
function didExpireAtExpirationTime(
root: FiberRoot,
currentTime: ExpirationTime,
): void {
const expirationTime = root.expirationTime;
// root 已经过期
if (expirationTime !== NoWork && currentTime >= expirationTime) {
// The root has expired. Flush all work up to the current time.
//
root.nextExpirationTimeToWorkOn = currentTime;
}
}
是否有 deadline
的区分异步/同步,用来判断一帧的渲染时间内还有没有时间留给 React 更新的
循环渲染 Root 的条件:循环不同 root 和不同优先级任务来更新
超过时间片的处理:deadline 这一帧的时间到了把执行权交回浏览器
shouldYield 方法用来判断一帧渲染时间内是否还有剩余时间留个React更新,改变全局变量 deadlineDidExpire
,为 false 表示还有时间留给React更新
function shouldYield() {
if (deadlineDidExpire) {
return true;
}
// deadline.timeRemaining 返回的值表示一帧渲染时间还有剩余, 直接return false, 此时deadlineDidExpire 为false
if (
deadline === null ||
deadline.timeRemaining() > timeHeuristicForUnitOfWork
) {
// Disregard deadline.didTimeout. Only expired work should be flushed
// during a timeout. This path is only hit for non-expired work.
return false;
}
deadlineDidExpire = true;
return true;
}
function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
deadline = dl;
// Keep working on roots until there's no more work, or until we reach
// the deadline.
findHighestPriorityRoot();
// 异步
if (deadline !== null) {
// 重新计算当前渲染时间
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime) &&
// 还有剩余时间更新 或者 任务已经过期需要强制输出
(!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
) {
performWorkOnRoot(
nextFlushedRoot,
nextFlushedExpirationTime,
// 过期为 true,未过期为 false
currentRendererTime >= nextFlushedExpirationTime,
);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
} else {
// 同步
while (
// root 非空
nextFlushedRoot !== null &&
// root 有更新
nextFlushedExpirationTime !== NoWork &&
// minExpirationTime 为 Sync,那么就只能是处理 nextFlushedExpirationTime 为Sync的情况,即同步执行的情况
(minExpirationTime === NoWork ||
minExpirationTime >= nextFlushedExpirationTime)
) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
findHighestPriorityRoot();
}
}
// We're done flushing work. Either we ran out of time in this callback,
// or there's no more work left with sufficient priority.
// If we're inside a callback, set this to false since we just completed it.
if (deadline !== null) {
callbackExpirationTime = NoWork;
callbackID = null;
}
// If there's work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(
((nextFlushedRoot: any): FiberRoot),
nextFlushedExpirationTime,
);
}
// Clean-up.
deadline = null;
deadlineDidExpire = false;
finishRendering();
}
nextFlushedRoot
, root对应的 expirationTime
赋值给 nextFlushedExpirationTime
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
function findHighestPriorityRoot() {
let highestPriorityWork = NoWork;
let highestPriorityRoot = null;
// 有 root 的情况
if (lastScheduledRoot !== null) {
let previousScheduledRoot = lastScheduledRoot;
let root = firstScheduledRoot;
while (root !== null) {
const remainingExpirationTime = root.expirationTime;
// Nowork 表示节点没有更新
if (remainingExpirationTime === NoWork) {
// This root no longer has work. Remove it from the scheduler.
// 只有一个 root 的情况
if (root === root.nextScheduledRoot) {
// This is the only root in the list.
root.nextScheduledRoot = null;
firstScheduledRoot = lastScheduledRoot = null;
break;
}
// ....
} else {
// root 上有更新
if (
highestPriorityWork === NoWork ||
remainingExpirationTime < highestPriorityWork
) {
// Update the priority, if it's higher
// 找更新优先级最高的 root 和它对应的 expirationTime
highestPriorityWork = remainingExpirationTime;
highestPriorityRoot = root;
}
if (root === lastScheduledRoot) {
break;
}
if (highestPriorityWork === Sync) {
// Sync is highest priority by definition so
// we can stop searching.
break;
}
previousScheduledRoot = root;
root = root.nextScheduledRoot;
}
}
}
nextFlushedRoot = highestPriorityRoot;
nextFlushedExpirationTime = highestPriorityWork;
}
isRendering
置为 true
,执行完后为 false
renderRoot
渲染阶段completeRoot
提交(commit)阶段 finishedWork
是已经完成 renderRoot
渲染阶段的任务,只有 renderRoot
后才不为 null
completeRoot
提交。没有剩余时间则等到下个时间段进入 performWorkOnRoot
判断function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isExpired: boolean,
) {
isRendering = true;
// Check if this is async work or sync/expired work.
// 同步任务 或者 过期任务
if (deadline === null || isExpired) {
// Flush work without yielding.
// TODO: Non-yieldy work does not necessarily imply expired work. A renderer
// may want to perform some work without yielding, but also without
// requiring the root to complete (by triggering placeholders).
let finishedWork = root.finishedWork;
// 上一次 renderRoot 完,但没有时间 completeRoot 了
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
// suspense 功能相关
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
// 表示这个任务不可中断(同步或过期任务不可中断)
const isYieldy = false;
// renderRoot 之后赋值 finishedWork
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
// Flush async work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
// 异步任务是可以中断的
const isYieldy = true;
// renderRoot 之后赋值 finishedWork
renderRoot(root, isYieldy, isExpired);
finishedWork = root.finishedWork;
// 可能被中断,被中断 finishedWork 为null
if (finishedWork !== null) {
// We've completed the root. Check the deadline one more time
// before committing.
// 还有剩余时间更新则继续 completeRoot
if (!shouldYield()) {
// Still time left. Commit the root.
completeRoot(root, finishedWork, expirationTime);
} else {
// 没有时间更新了, 待到下个时间段进入 performWorkOnRoot 判断
// There's no time left. Mark this root as complete. We'll come
// back and commit it later.
root.finishedWork = finishedWork;
}
}
}
}
isRendering = false;
}
// ReactFiberStack.js
// 所有值存储的数组
const valueStack: Array<any> = [];
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
// 入栈
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
// index 累加
index++;
valueStack[index] = cursor.current;
// 新的 value 不会推入栈
cursor.current = value;
}
// 出栈
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
return;
}
// 老的值赋值出来
cursor.current = valueStack[index];
// 清空 index 位置的值
valueStack[index] = null;
// 累减
index--;
}
cursor
context
contextStackCursor
: 记录当前组件和父组件一起提供给子组件的 childContext
对象,默认是 {}
didPerformWorkStackCursor
: 标记子树的 context 是否变化的, false
表示可以跳过更新,记录的值其实是 shouldUpdate
valueCursor
: 新 context
的 cursor// ReactFiberContext.js
// 老 context 的两种 cursor
const emptyContextObject = {};
// A cursor to the current merged context object on the stack.
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// ReactFiberNewContext.js
// 新 context cursor
const valueCursor: StackCursor<mixed> = createCursor(null);
父组件提供 getChildContext
方法、声明 childContextTypes
属性,子组件声明 contextTypes
属性的方式获取 context
的问题
context provider
会合并 key
相同的属性classComponent
才能使用 getChildContext
方法给子树提供 childContext
function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
// ...
}
function pushHostRootContext(workInProgress) {
const root = (workInProgress.stateNode: FiberRoot);
// 初次渲染如果有 pendingContext
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
// Should always be set
// 除了初次渲染 push 的值是 false,表明目前 context 没有变化
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
// updateClassComponent
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
// 判断是否是老 context 方式提供 context ,如果是,则 push,并将hasContext 置为 true ,在 finishClassComponent 中会用到
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
// 新的 context 相关
prepareToReadContext(workInProgress, renderExpirationTime);
// ...
}
function isContextProvider(type: Function): boolean {
const childContextTypes = type.childContextTypes;
return childContextTypes !== null && childContextTypes !== undefined;
}
updateComponentClass
时首先 push
一次老的 context
previousContext
赋值为 contextStackCursor.current
即上一次 push
的值,这个值记录着父组件提供的 context
的集合updateComponentClass
时并不知道组件是否需要更新,并不知道是否有新的 contetxtfinishClassComponent
中判断如果组件需要更新了,会重新计算新的 context
,执行 invalidateContextProvider
将计算过后的 mergedContext
挂载在 __reactInternalMemoizedMergedChildContext
属性上//
function pushContextProvider(workInProgress: Fiber): boolean {
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyContextObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
// 上一次 push 时的值,即对应父组件的 context
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);
return true;
}
finishClassComponent
中如果不需要更新且没有错误则跳过更新,并且如果 hasContext
为 true
则会执行 invalidateContextProvider
,这里 didChange
是 false
,即对应 push
的 didPerformWorkStackCursor
值为 false
,表示组件可以跳过更新,在往下执行子树的 beginWork
时就会根据 !hasContextChanged()
判断是否可以跳过更新finishClassComponent
如果组件需要更新,也会执行 invalidateContextProvider
,这里 didChange
为 true
PureComponent
不会判断 context
是否变化// finishClassComponent
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
// Refs should update even if shouldComponentUpdate returns false
markRef(current, workInProgress);
const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
// 不需要更新且没有错误则跳过更新
if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
// updateClassComponent 中使用了老 context 后会赋值为 true
// didChange 为 false
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
// 跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
// ...
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
// invalidateContextProvider
function invalidateContextProvider(
workInProgress: Fiber,
type: any,
didChange: boolean, // 组件是否有更新
): void {
const instance = workInProgress.stateNode;
// 组件需要更新
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
// 重新计算新的合并过的 context,(会合并父组件的 context )
const mergedContext = processChildContext(
workInProgress,
type,
previousContext, // 上一次 push 的 context 的值
);
// 挂载到 __reactInternalMemoizedMergedChildContext 属性上
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
// finishClassComponent 之前在 updateClassComponent 的时候 push 了一次老的,如果有更新,需要将老的 pop
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
// 再 push 新计算出来的 context
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {
// 没有更新,有没有更新都需要将 didPerformWorkStackCursor.current 同步 didChange, 如果不同步 false 的情况那组件 beginWork 时会一直更新,没有可以跳过更新的情况
// pop 老的
pop(didPerformWorkStackCursor, workInProgress);
// push 新的
// 仍需要 push didChange 将 didPerformWorkStackCursor.current = false
push(didPerformWorkStackCursor, didChange, workInProgress);
}
}
context
,相同 key
属性会被覆盖function processChildContext(
fiber: Fiber,
type: any,
parentContext: Object, // 全局变量 previousContext
): Object {
const instance = fiber.stateNode;
const childContextTypes = type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
// 如果没有 getChildContext 方法就直接返回父组件的 context
if (typeof instance.getChildContext !== 'function') {
return parentContext;
}
let childContext;
// 获取新的 context
childContext = instance.getChildContext();
// 合并 context
return {...parentContext, ...childContext};
}
beginWork
开始时会调用这个方法根据 context
是否变化来判断是否可以跳过组件更新,这也是影响性能的一个地方!hasLegacyContextChanged()
即为 false
时跳过更新,这个值实际存储的是 didChange
,跟 shouldComponentUpdate
有关context
变化,各个类型的组件分别 push context
function hasContextChanged(): boolean {
return didPerformWorkStackCursor.current;
}
// beginWork
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
// 非首次渲染
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps === newProps && // 新老 props 相等
!hasLegacyContextChanged() && // 父组件的 context 没有变化
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = workInProgress.type;
// 有 childContextTypes 属性
if (isLegacyContextProvider(Component)) {
// 入栈
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
break;
case SuspenseComponent: {
// ...
break;
}
}
// 跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// ....
}
popContext
: pop
的顺序和 push
时相反,最后 push
的 didPerformWorkStackCursor
先 pop
,确保从 valueStack
中取值正确function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
break;
case LazyComponent:
break;
case SimpleMemoComponent:
case FunctionComponent:
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
break;
}
// ....
}
// ...
}
// popLegacyContext => popContext
function popContext(fiber: Fiber): void {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
getUnmaskedContext
获取合并过的 context
getMaskedContext
从 getUnmaskedContext
的结果里取需要的属性// updateFunctionComponent
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
// ...
return workInProgress.child;
}
// updateClassInstance
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
const oldProps = workInProgress.memoizedProps;
instance.props = oldProps;
const oldContext = instance.context;
// 新的 context api 使用方式
const contextType = ctor.contextType;
let nextContext;
if (typeof contextType === 'object' && contextType !== null) {
nextContext = readContext(contextType);
} else {
// 老的 context
// 组件自己声明的 contextTypes 是给组件的子节点用的,自己要使用 context 来更新,应该用 parent 提供的 context,即 previousContext
// 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
// 子组件从父组件合并过后的 context 中选择性属性
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
}
// ...
}
function getUnmaskedContext(
workInProgress: Fiber,
Component: Function,
didPushOwnContextIfProvider: boolean, // 读 context 一般都为 true
): Object {
// 如果自己也是 context provider,且已经 push 过一次了,这里应该读 previousContext
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
// 不是 provider 则读当前的 cursor 就可以
// 或者 mountIndeterminateComponent 函数里 didPushOwnContextIfProvider 为 false
return contextStackCursor.current;
}
context
中获取组件声明的 contextTypes
中需要的数据function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
// 如果有缓存则从缓存里读
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
// 根据 contextTypes 上声明的 key 读取父组件合并过后的 context 中的值
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
// 缓存 context
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
}
// 缓存
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
Context.Provider
Context.Consumer
context
的提供方和订阅方都是独立的context
,并创建 Provider
组件类型,Consumer
组件类型即创建的 context
ContextConsumer = REACT_CONTEXT_TYPE;
function createContext<T>(
defaultValue: T,
calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
const context: ReactContext<T> = {
$$typeof: REACT_CONTEXT_TYPE,
_calculateChangedBits: calculateChangedBits,
// As a workaround to support multiple concurrent renderers, we categorize
// some renderers as primary and others as secondary. We only expect
// there to be two concurrent renderers at most: React Native (primary) and
// Fabric (secondary); React DOM (primary) and React ART (secondary).
// Secondary renderers store their context values on separate fields.
_currentValue: defaultValue,
_currentValue2: defaultValue,
// These are circular
Provider: (null: any),
Consumer: (null: any),
};
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
let hasWarnedAboutUsingNestedContextConsumers = false;
let hasWarnedAboutUsingConsumerProvider = false;
if (__DEV__) {
// ....
} else {
context.Consumer = context;
}
if (__DEV__) {
context._currentRenderer = null;
context._currentRenderer2 = null;
}
return context;
}
pushProvider
的时候设置 context._currentValue = nextValue
; 最后 consumer
是从这个值中读取的// beginWork
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const providerType: ReactProviderType<any> = workInProgress.type;
// _context 指向创建的 context 对象(也是 Consumer)
const context: ReactContext<any> = providerType._context;
const newProps = workInProgress.pendingProps;
const oldProps = workInProgress.memoizedProps;
const newValue = newProps.value;
// push 新的 value
pushProvider(workInProgress, newValue);
if (oldProps !== null) {
const oldValue = oldProps.value;
// 模拟 Object.is
// 如果 newValue, oldValue 相等返回 0
// 如果不相等则返回 MAX_SIGNED_31_BIT_INT
const changedBits = calculateChangedBits(context, newValue, oldValue);
if (changedBits === 0) {
// No change. Bailout early if children are the same.
if (
oldProps.children === newProps.children &&
!hasLegacyContextChanged()
) {
// newValue, oldValue相等 且新老 children 相同,且没有老 context变化的条件下,可以跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
// The context value changed. Search for matching consumers and schedule
// them to update.
// 有更新,则遍历子节点对有 firstContextDependency 的 classComponent 类型的节点创建一个 forceUpdate 的更新
propagateContextChange(
workInProgress,
context,
changedBits,
renderExpirationTime,
);
}
}
const newChildren = newProps.children;
// 调和子节点
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
return workInProgress.child;
}
function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {
// context 对象
const context: ReactContext<T> = providerFiber.type._context;
// ReactDOM 下 isPrimaryRenderer 为 true
if (isPrimaryRenderer) {
// push valueCursor 值为 context._currentValue
// valueCursor 记录的是当前这棵树下面一共经历了几个 provider 对应的值
push(valueCursor, context._currentValue, providerFiber);
// consumer 读取值是从 context._currentValue 读取的,与 v alueCursor 无关
context._currentValue = nextValue;
} else {
}
}
Provider
组件firstContextDependency
的节点,如果节点依赖的 context
有更新(非 setState
的更新),对于 classComponent
类型主动创建一个 tag
为 forceUpdate
的 update
来强制更新,并操作 expirationTime
和父节点的 childExpirationTime
firstContextDependency
是在 updateContextConsumer
中 prepareToReadContext
方法初始化,在 readContext
中被赋值function propagateContextChange(
workInProgress: Fiber,
context: ReactContext<mixed>,
changedBits: number,
renderExpirationTime: ExpirationTime,
): void {
// 第一个子节点
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
// Visit this fiber.
// firstContextDependency 这个属性在 updaterConsumer、 updateClassComponent、updateFunctionComponent 中的 prepareToReadContext 中被初始化
let dependency = fiber.firstContextDependency;
if (dependency !== null) {
do {
// Check if the context matches.
if (
// 表示遍历过程中的这个组件依赖于 context,如果 context 变化则需要重新渲染
dependency.context === context &&
// dependency.observedBits 只要不是 0 则 这个条件为真, 在 updateContextConsumer 中一般默认设置为 MAX_SIGNED_31_BIT_INT
// changedBits 有变化的情况则值为 MAX_SIGNED_31_BIT_INT
// 同时 dependency.observedBits 和 changedBits 有相交的部分则说明依赖的部分也变化了
(dependency.observedBits & changedBits) !== 0
) {
// 满足以上条件,组件需要更新
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
// context 更新,但没有 setState 的创建 update ,则主动创建一个 update,tag 为 ForceUpdate 强制更新
const update = createUpdate(renderExpirationTime);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
// this render is thrown away. Since it's a race condition, not sure it's
// worth fixing.
enqueueUpdate(fiber, update);
}
// 创建 update 的同时,操作 expirationTime
if (
fiber.expirationTime === NoWork ||
fiber.expirationTime > renderExpirationTime // 节点的更新优先级低于当前更新的优先级
) {
// 则重新赋值节点的expirationTime 保证在这一次更新中能更新到
fiber.expirationTime = renderExpirationTime;
}
let alternate = fiber.alternate;
// 同步 alternate
if (
alternate !== null &&
(alternate.expirationTime === NoWork ||
alternate.expirationTime > renderExpirationTime)
) {
alternate.expirationTime = renderExpirationTime;
}
// Update the child expiration time of all the ancestors, including
// the alternates.
// 同步父节点的 childExpirationTime
let node = fiber.return;
while (node !== null) {
alternate = node.alternate;
if (
node.childExpirationTime === NoWork ||
node.childExpirationTime > renderExpirationTime
) {
node.childExpirationTime = renderExpirationTime;
if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > renderExpirationTime)
) {
alternate.childExpirationTime = renderExpirationTime;
}
} else if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > renderExpirationTime)
) {
alternate.childExpirationTime = renderExpirationTime;
} else {
// Neither alternate was updated, which means the rest of the
// ancestor path already has sufficient priority.
break;
}
node = node.return;
}
}
nextFiber = fiber.child;
// dependency 也是单链表结构
dependency = dependency.next;
} while (dependency !== null);
} else if (fiber.tag === ContextProvider) {
// Don't scan deeper if this is a matching provider
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else {
// 往 child 找
// Traverse down.
nextFiber = fiber.child;
}
// 遍历子树的每个一个节点,找到有 firstContextDependency 的然后对它创建 update
if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
nextFiber.return = fiber;
} else {
// No child. Traverse to next sibling.
nextFiber = fiber;
// 如果没有 child 则往兄弟节点找
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// We're back to the root of this subtree. Exit.
nextFiber = null;
break;
}
let sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
// No more siblings. Traverse up.
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
workInProgress.type
即 createContext
返回的 context
对象prepareToReadContext
初始化 firstContextDependency
,赋值全局变量 currentlyRenderingFiber
为当前 workInProgress
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
let context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
// consumer 的 children 为一个函数,这个函数的参数即 provider 提供的 value
const render = newProps.children;
// 初始化 currentlyRenderingFiber 、workInProgress.firstContextDependency
prepareToReadContext(workInProgress, renderExpirationTime);
// 读取 context._currentValue ,
const newValue = readContext(context, newProps.unstable_observedBits);
let newChildren;
if (__DEV__) {
// ...
} else {
// 调用 render 获取children
newChildren = render(newValue);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
// 调和子节点
reconcileChildren(current, workInProgress, newChildren, renderExpirationTime);
return workInProgress.child;
}
fiber
上添加 firstContextDependency
属性,值为 null
currentlyRenderingFiber
为当前 fiber
节点lastContextDependency
值为 null
,用于构建单链表结构function prepareToReadContext(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): void {
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;
lastContextWithAllBitsObserved = null;
// Reset the work-in-progress list
workInProgress.firstContextDependency = null;
}
firstContextDependency
到 lastContextDependency
的 单链表结构context._currentValue
做为 context
function readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean, // => undefined
): T {
// 首次进入这个函数 lastContextWithAllBitsObserved 为 null
if (lastContextWithAllBitsObserved === context) {
// Nothing to do. We already observe everything in this context.
// unstable_observedBits 目前版本下为 undefined
} else if (observedBits === false || observedBits === 0) {
// Do not observe any updates.
} else {
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
lastContextWithAllBitsObserved = ((context: any): ReactContext<mixed>);
// 赋值 resolvedObservedBits ,
resolvedObservedBits = MAX_SIGNED_31_BIT_INT;
} else {
resolvedObservedBits = observedBits;
}
let contextItem = {
context: ((context: any): ReactContext<mixed>),
// 这个值即在 updateContextProvider 中的 propagateContextChange 函数中判断 (dependency.observedBits & changedBits) !== 0
observedBits: resolvedObservedBits,
next: null,
};
if (lastContextDependency === null) {
// This is the first dependency in the list
// 构建 firstContextDependency 到 lastContextDependency 的 单链表结构
currentlyRenderingFiber.firstContextDependency = lastContextDependency = contextItem;
} else {
// Append a new context item.
lastContextDependency = lastContextDependency.next = contextItem;
}
}
// ReactDOM 下 isPrimaryRenderer 为 true,直接返回 context._currentValue
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
updateFunctionComponent、updateClassComponent
也会执行 prepareToReadContext
这个方法,对于 classComponent
组件还可以声明 contextType
的方法来获取新 context
,在 constructClassInstance、mountClassInstance、resumeMountClassInstance、updateClassInstance
方法中会判断 contextType
是否存在,如果存在则在赋值 instance.context = readContext(contextType)
, 从而能在声明周期方法里通过 this.context
获取 context
// prepareToReadContext
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
//....
}
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
// 新的 context 相关, 设置 workInProgress.firstContextDependency 为null
prepareToReadContext(workInProgress, renderExpirationTime);
// ....
}
// readContext
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = null;
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
// 新 context 获取方式
context = readContext((contextType: any));
} else {
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
const contextTypes = ctor.contextTypes;
isLegacyContextConsumer =
contextTypes !== null && contextTypes !== undefined;
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
}
// 往 instance 上挂载 context
const instance = new ctor(props, context);
// ....
}
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
}
const instance = workInProgress.stateNode;
instance.props = newProps;
instance.state = workInProgress.memoizedState;
instance.refs = emptyRefsObject;
const contextType = ctor.contextType;
if (typeof contextType === 'object' && contextType !== null) {
// 挂载 context
instance.context = readContext(contextType);
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
}
}
function resumeMountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): boolean {
const instance = workInProgress.stateNode;
const oldProps = workInProgress.memoizedProps;
instance.props = oldProps;
const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext;
if (typeof contextType === 'object' && contextType !== null) {
// 挂载 context
nextContext = readContext(contextType);
} else {
const nextLegacyUnmaskedContext = getUnmaskedContext(
workInProgress,
ctor,
true,
);
nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext);
}
}
初始渲染
ReactDOM.render 、ReactDOM.hydrate
组件内更新
setState
forceUpdate
replaceState(即将被舍弃)
ReactDOM对象有render
、hydrate
两个方法,render
方法是用在浏览器环境内, hydrate
用于服务端渲染,区别在于调用 legacyRenderSubtreeIntoContainer
方法第四个参数 false
和 true
.
const ReactDOM = {
// ...
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
render(
element: React$Element<any>, // React element
container: DOMContainer, // 挂在节点
callback: ?Function, // 渲染结束的callback
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
}
legacyRenderSubtreeIntoContainer
作用主要是生成 ReactRoot
并调用 ReactRoot
实例的 render
方法
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
...
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
// container 就是首次渲染传入的<div id="root">, 这个dom 肯定不存在 _reactRootContainer 属性
if (!root) {
// Initial mount
// 初次渲染,创建ReactRoot,并创建FiberRoot
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 封装回调
if (typeof callback === 'function') {
...
}
// Initial mount should not be batched.
// 初始渲染不是批量更新
DOMRenderer.unbatchedUpdates(() => {
// render, hydrate 传入的 parentComponent 都是 null
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// 调用 ReactRoot 的render方法
root.render(children, callback);
}
});
} else {
// 更新逻辑
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
return DOMRenderer.getPublicRootInstance(root._internalRoot);
}
legacyCreateRootFromDOMContainer
函数的作用判断是不是 hydrate
渲染,ReactDOM.render
当然为 false
,接着会判断 rootElement
有没有 ROOT_ATTRIBUTE_NAME
即 data-reactroot
属性来判断是不是服务端渲染,如果不是则 while 循环将 root 所有子节点全部删掉,返回new ReactRoot(container, isConcurrent, shouldHydrate)
function legacyCreateRootFromDOMContainer(
container: DOMContainer, // ReactDOM.render 传入的 root
forceHydrate: boolean, // ReactDOM.render 为false
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
if (__DEV__) {
...
}
// 把 root 的所有子节点删掉
container.removeChild(rootSibling);
}
}
if (__DEV__) {
...
}
// Legacy roots are not async by default.
// root 节点创建时同步的,isConcurrent: false
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
接着看ReactRoot
构造函数,构造函数里调用了 ReactFiberReconciler 里的 createContainer
方法,这个方法调用 createFiberRoot
生成一个 FiberRoot
对象,最后挂载到实例的 _internalRoot
上,
function ReactRoot(
container: Container,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
// ReactFiberReconciler.js
export function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
legacyRenderSubtreeIntoContainer
方法里 root.render
调用的最终 ReactRoot
构造函数的 protoType
上定义了 render
方法,而它调用的是 ReactFiberReconciler 里的 updateContainer
。
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
// FiberRoot
// FiberRoot 是根据 ReactDOM.render 方法的第二个参数创建出来的,创建的过程中同时创建 RootFiber, RootFiber.stateNode = FiberRoot, FiberRoot.current = RootFiber
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};
DOMRenderer.updateContainer
中计算出一个 expirationTime
传入了 updateContainerAtExpirationTime
.
// ReactFiberReconciler.js
export function updateContainer(
element: ReactNodeList, // <App />
container: OpaqueRoot, // FiberRoot
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
// RootFiber
const current = container.current;
// 创建一个时间差
const currentTime = requestCurrentTime();
// 计算出一个时间,ConcurrentMode 会用到
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
FiberRoot
和 RootFiber
接下来会讲到,先看 updateContainerAtExpirationTime
创建更新的过程,调用scheduleRootUpdate
方法将 RootFiber
、 element
、计算出的expirationTime
、 callback
传入,在这个方法里,首先会调用createUpdate
创建一个 update
对象,enqueueUpdate
方法将 update
对象加入到 fiber
对象上的 updateQueue
里,scheduleWork
即开始执行调度。创建更新的过程到此为止。
export function updateContainerAtExpirationTime(
element: ReactNodeList, // <App />
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
if (__DEV__) {
...
}
// 目前版本React拿不到context,因为 parentComponent 为null
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
...
}
// update对象是用来标记React应用中需要更新的节点
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
// 设置update的相关属性
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
// 把update加入到 fiber 的updateQueue 里,一个节点上可能会产生很多次更新,需要 batchUpdates (批量更新)
enqueueUpdate(current, update);
// 有更新产生,告诉 React 开始调度
scheduleWork(current, expirationTime);
return expirationTime;
}
初次渲染 传入 APP 组件和 getElementById(root) 执行 ReactDOM.render
返回并执行 legacyRenderSubtreeIntoContainer
legacyRenderSubtreeIntoContainer
方法调用 legacyCreateRootFromDOMContainer
方法把 getElementById(root) 里的子节点清空,并把返回值 new ReactRoot
挂载到 getElementById(root) 节点的 _reactRootContainer 属性上
ReactRoot 生成实例时调用 react-reconcile 模块的 createContainer 传入 getElementById(root) 执行 createFiberRoot
生成一个 FiberRoot
对象挂载到实例的 _internalRoot
legacyRenderSubtreeIntoContainer 方法里 root.render
方法实际是调用 ReactRoot 原型上的render方法
ReactRoot.prototype.render 把子节点和实例生成的 _internalRoot FiberRoot 对象传入 react-reconcile 模块的 updateContainer 中
在 updateContainer 中 react 计算出一个 expirationTime 传入 updateContainerAtExpirationTime 调用 scheduleRootUpdate 中做三件事
createUpddate 创建update对象来标记 react 需要更新的点
enqueueUpdate 将 update 加入到 RootFiber 的 updateQueue 中
scheduleWork 根据任务的优先级进行调度更新
ReactDOM.render App
组件时会创建 FiberRoot -> createHostRootFiber
,标记这个 fiber的 tag 为 HostRoot
,创建 FiberRoot
的同时创建 RootFiber
,对应 ReactDOM.render
方法的第二个参数对应的 Dom 节点
function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(isConcurrent);
// ....
}
function createHostRootFiber(isConcurrent: boolean): Fiber {
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
beginWork
时首次更新的是 HostRoot
,在 updateHostRoot
中会调用 reconcileChildFibers
调和子节点,在reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps
依次将 children element
创建成对应的 Fiber
对象, 默认 fiberTag
为 IndeterminateComponent
,根据 React.createElement
返回的 type
将 fiberTag
标记为对应的组件类型,将创建完的 children
返回到 workLoop
,遍历执行 performUnitOfWork -> beginWork
, 根据这个 children
的 fiberTag
对应依次更新,如此循环先创建,再更新。workLoop
进行循环单元更新, 对整棵 fiberTree 都遍历一遍nextUnitOfWork
是每个节点自己更新完之后返回的第一个子节点nextUnitOfWork
首次赋值为 createWorkInProgress
拷贝的一份 fiber
节点,以后的操作都是修改的 nextUnitOfWork, 防止改变当前 fiberTreefunction renderRoot(
root: FiberRoot,
isYieldy: boolean,
isExpired: boolean,
): void {
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;
const expirationTime = root.nextExpirationTimeToWorkOn;
// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
// 说明将要执行的任务 root 和 expirationTime 和 nextRenderExpirationTime、nextRoot 预期的不一样, 可能是之前任务被高优先级的任务打断了。
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null // 更新结束 fiber 的 child,下一个节点, 首次为 null
) {
// Reset the stack and start working from the root.
// 重置
resetStack();
nextRoot = root;
// root.nextExpirationTimeToWorkOn;
nextRenderExpirationTime = expirationTime;
// 将 fiber 拷贝成 workInProgress 对象操作,不直接操作 fiber 节点
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
let didFatal = false;
startWorkLoopTimer(nextUnitOfWork);
// 开始循环 workLoop
do {
try {
// 同步或过期任务则 isYieldy 为 false 表示不可中断
workLoop(isYieldy);
} catch (thrownValue) {
// ... catch 错误
// 致命错误
if (didFatal) { }
// workLoop break 中断的错误
if (nextUnitOfWork !== null) { }
// 可处理的错误
if (nextRenderDidError) { }
}
break; // 遇到了某种错误跳出
} while (true);
// ...
const rootWorkInProgress = root.current.alternate;
// Ready to commit.
// 最后 commit root
onComplete(root, rootWorkInProgress, expirationTime);
}
function workLoop(isYieldy) {
// 同步或过期任务 isYieldy 为false 不可中断
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
// !shouldYield() 还有剩余时间用来更新
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
let next;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
// 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
next = beginWork(current, workInProgress, nextRenderExpirationTime);
// 将最新的 props 赋值给目前正在用的 props
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
// Record the render duration assuming we didn't bailout (or error).
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
// 更新到最后的节点,叶子节点
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 往上遍历
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
fiber
树是否需要更新,不需要更新的则进行优化function beginWork(
// RootFiber
current: Fiber | null,
// 与 nextUnitOfWork 对应,下一个将要更新的节点
workInProgress: Fiber,
// root.nextExpirationTimeToWorkOn 表示一次更新内最高优先级的更新
renderExpirationTime: ExpirationTime,
): Fiber | null {
// 节点自身的过期时间
const updateExpirationTime = workInProgress.expirationTime;
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
// 前后两次props 相同
oldProps === newProps &&
// context 相关
!hasLegacyContextChanged() &&
// 没有更新
(updateExpirationTime === NoWork ||
// 更新优先级没有当前更新优先级高
updateExpirationTime > renderExpirationTime)
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
// ...优化的过程
}
// 跳过当前节点及其子节点的更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;
// 节点是有更新的情况
switch (workInProgress.tag) {
// ...根据节点类型,执行对应组件类型的更新
}
}
fiber
的子树是否需要更新,如果需要更新则会 clone 一份老的 child 到 workInProgress.child
返回到 performUnitOfWork
的 next
再返回到 workLoop
的 nextUnitOfWork
循环更新子节点, 否则为return null
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
cancelWorkTimer(workInProgress);
if (current !== null) {
// Reuse previous context list
workInProgress.firstContextDependency = current.firstContextDependency;
}
// Check if the children have any pending work.
// 子节点最高优先级的更新
const childExpirationTime = workInProgress.childExpirationTime;
if (
// 没有更新
childExpirationTime === NoWork ||
// 子树的优先级低
childExpirationTime > renderExpirationTime
) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
// 直接跳过子树更新
return null;
} else {
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
// 子树有更新的情况
// 当前节点不需要更新,说明 child 没有更新,把老的 workInProgress.child 复制一份,并返回
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
fiber
的 expirationTIme
置为 Nowork
workInProgress.type
函数式组件为 function``,
class组件为
class` 构造函数,dom原生组件为div这种字符串penddingProps
,新的渲染产生的 props
一个 React 应用中一般只有一个 hostRoot
,对应的 Fiber
为 RootFiber
对象, 对应的 element
为 ReactDOM.render
方法的第二个参数 <div id="root" />
, 也就是 FiberRoot。
创建更新的过程,ReactDOM.render
-> ReactRoot.prototype.render
-> DOMRenderer.updateContainer
-> updateContainerAtExpirationTime
-> scheduleRootUpdate
function scheduleRootUpdate(
current: Fiber, // RootFiber
element: ReactNodeList, // ReactDOM.render 的第一个参数
expirationTime: ExpirationTime,
callback: ?Function,
) {
// 创建 update,这个update tag 为 UpdateState
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
// 这里的 payload 相当于 state ,跟 setState 调用效果相同
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
// 为 RootFiber 添加 updateQueue
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}
processUpdateQueue
得到一个 element
属性新 state
,从这个 element 开始创建和更新整个 fiber tree 。function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
invariant(
updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
// 首次 prevState 为null
const prevChildren = prevState !== null ? prevState.element : null;
// 执行 processUpdateQueue 将 updateQueue 执行得到有个 element 属性的新的state,
processUpdateQueue(
workInProgress,
updateQueue,
nextProps,
null,
renderExpirationTime,
);
// 拿到 scheduleRootUpdate 时的 update.payload
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
// 拿到 element 即 <App />
const nextChildren = nextState.element;
// 如果相同,则跳过更新,
// 一般不会在 RootFiber 上创建更新,而是在 ReactDOM.render 传入的第一个参数那个节点(app)上创建更新
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
// 服务端渲染相关,复用节点
resetHydrationState();
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const root: FiberRoot = workInProgress.stateNode;
// 服务端渲染相关
if (
(current === null || current.child === null) &&
root.hydrate &&
enterHydrationState(workInProgress)
) {
workInProgress.effectTag |= Placement;
// 认为这是第一次渲染
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
// ReactDOM.render -> 首次渲染时,current 和 workInProgress 都是存在的, 那么会走 reconcileChildFibers 更新调和
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
resetHydrationState();
}
return workInProgress.child;
}
Component
组件原型上的方法,他们调用的 enqueueSetState
和 enqueueForceUpdate
都是来自不同平台创建的 updater
对象上的,运行在浏览器中的 updater
对象来自 ReactFiberClassComponent.js 里的 classComponentUpdater
对象上。
// ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
// 作为参数让不同的平台来控制属性更新的方式
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function(partialState, callback) {
// 仅仅调用了 updater 上的方法 updater 是初始化的第三个参数的实例属性,跟平台相关
// 这里的 this 即对应组件(fiber 节点)
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
在 beginWork
-> updateClassComponent
中 constructClassInstance
创建组件 instance
时将 classComponentUpdater
挂载到 instance.updater
上
enqueueSetState
, enqueueForceUpdate
几乎是相同的, 其中 enqueueForceUpdate
只是将 update
对象的 tag
设置成了 ForceUpdate
enqueueSetState
方法的执行的操作和 updateContainer
几乎一模一样
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
// 从instanceMap中获取Fiber对象, inst 为调用setState的this,即那个组件
const fiber = ReactInstanceMap.get(inst);
// 创建当前时间
const currentTime = requestCurrentTime();
// 计算优先级时间
const expirationTime = computeExpirationForFiber(currentTime, fiber);
// 创建update对象
const update = createUpdate(expirationTime);
// 设置payload
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
// 设置callback
update.callback = callback;
}
// 将 update 对象加入 fiber 的 updateQueue 中
enqueueUpdate(fiber, update);
// 执行调度更新
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState(inst, payload, callback) {
// ...
},
enqueueForceUpdate(inst, callback) {
// ...
},
};
每一次进入调度队列的只有 FiberRoot 对象, 更新也是从 FiberRoot 对象上开始的。
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
// 找到 FiberRoot,并更新 FiberTree 上所有 Fiber 节点的expirationTime
const root = scheduleWorkToRoot(fiber, expirationTime);
// 没有 FiberRoot 返回
if (root === null) {
return;
}
if (
!isWorking && // 没有执行渲染, isWorking 表示有任务正在进行中
nextRenderExpirationTime !== NoWork && // 任务是个异步的,执行到一半了,把执行权交还给浏览器执行
expirationTime < nextRenderExpirationTime // 新的任务优先级高于现在的任务
) {
// 新的高优先级的任务打断了老的低优先级任务,优先执行高优先级的任务
// This is an interruption. (Used for performance tracking.)
// 用来记录
interruptedBy = fiber;
// 重置并回退之前更新过的state
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
// 没有正在工作, isWorking 和 isCommitting 对应两个不同阶段,isWorking包含isCommitting 和 rendering, 简单说就是要么处于没有 work 的状态,要么只能在 commit 阶段
!isWorking ||
// 正在提交,也就是更新dom 树的渲染阶段
isCommitting ||
// ...unless this is a different root than the one we're rendering.
// 不同的 root,一般不存在不同
nextRoot !== root
) {
// 调用了 markPendingPriorityLevel 后 rootExpirationTime 不一定是传入进来的 expirationTime
const rootExpirationTime = root.expirationTime;
// 开始调度
requestWork(root, rootExpirationTime);
}
// componentWillUpdate、componentDidUpdate 函数里调用 setState,死循环。。
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
}
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
// Update the source fiber's expiration time
if (
// 没有更新操作
fiber.expirationTime === NoWork ||
// 之前有更新产生且没完成,但当前fiber更新的优先级低于当前的更新
fiber.expirationTime > expirationTime
) {
// 设置成高优先级的 expirationTime
fiber.expirationTime = expirationTime;
}
// 同理更新 alternate 的expirationTime
let alternate = fiber.alternate;
if (
alternate !== null &&
(alternate.expirationTime === NoWork ||
alternate.expirationTime > expirationTime)
) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
// return 即父节点
let node = fiber.return;
let root = null;
// RootFiber 的 return 才为null
if (node === null && fiber.tag === HostRoot) {
// 返回 FiberRoot
root = fiber.stateNode;
} else {
// 循环查找 RootFiber 对象
while (node !== null) {
alternate = node.alternate;
// childExpirationTime 是Fiber子树当中优先级最高的 expirationTime
// 如果符合条件,将childExpirationTime设置成更高优先级的更新
if (
node.childExpirationTime === NoWork ||
node.childExpirationTime > expirationTime
) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
(alternate.childExpirationTime === NoWork ||
alternate.childExpirationTime > expirationTime)
) {
alternate.childExpirationTime = expirationTime;
}
// 找到 RootFiber 结束循环
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
// 继续循环
node = node.return;
}
}
return root;
}
function resetStack() {
// nextUnitOfWork 是用来记录下一个要更新的节点
if (nextUnitOfWork !== null) {
// 向上找被打断的任务
let interruptedWork = nextUnitOfWork.return;
while (interruptedWork !== null) {
// 退回状态,因为更高优先级的任务也是从头更新的,避免更新过的组件 state 错乱
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
// 重置初始值,进行新的优先级任务更新
nextRoot = null;
nextRenderExpirationTime = NoWork;
nextLatestAbsoluteTimeoutMs = -1;
nextRenderDidError = false;
nextUnitOfWork = null;
}
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将当前 FiberRoot 优先级设置为最高
addRootToSchedule(root, expirationTime);
// isRendering表示调度已经执行了,循环开始了,render 阶段不需要重新 requestWork 了
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
// 批量处理相关
// 调用 setState 时在 enqueueUpdates 前 batchedUpdates 会把 isBatchingUpdates 设置成 true, 和事件系统有关
// 为 true 则为调用 performSyncWork,则没有调度更新
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, true);
}
return;
}
// TODO: Get rid of Sync and use current time?
// 同步更新
if (expirationTime === Sync) {
performSyncWork();
} else {
// 异步调度,独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
// Add the root to the schedule.
// Check if this root is already part of the schedule.
// 首次渲染 root 没有进入调度
if (root.nextScheduledRoot === null) {
// This root is not already scheduled. Add it.
root.expirationTime = expirationTime;
// 只有一个 root 的情况
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
} else {
// 多个 root 构建单链表结构
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
}
} else {
// This root is already scheduled, but its priority may have increased.
// 传入的 FiberRoot 已经进入过调度
const remainingExpirationTime = root.expirationTime;
if (
remainingExpirationTime === NoWork ||
// root的 expirationTime 优先级低
expirationTime < remainingExpirationTime
) {
// Update the priority.
// 把当前 root 的优先级设置为当前优先级最高的
root.expirationTime = expirationTime;
}
}
}
demo
import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'
export default class BatchedDemo extends React.Component {
state = {
number: 0,
}
componentDidMount () {
const btn = document.getElementById('btn');
// 方法四, 结果输出 1, 2, 3
btn.addEventListener('click', this.handleClick);
}
handleClick = () => {
// 方法一, 结果输出 0, 0, 0
// 事件处理函数自带`batchedUpdates`
this.countNumber()
// 方法二, 结果输出 1, 2, 3
// setTimeout中没有`batchedUpdates`
// setTimeout(() => {
// this.countNumber()
// }, 0)
// 方法三, 结果输出 0, 0, 0
// 主动`batchedUpdates`
// setTimeout(() => {
// batchedUpdates(() => this.countNumber())
// }, 0)
/**
* setState为函数式调用,记录在firstUpdate的单链表结构里的payload为 function
*/
}
countNumber() {
const num = this.state.number
this.setState({
number: num + 1,
});
console.log(this.state.number)
this.setState(() => {
return {
number: num + 2,
};
});
console.log(this.state.number)
this.setState({
number: num + 3,
})
console.log(this.state.number)
}
render() {
// React 自定义事件调用
return <button onClick={this.handleClick} >Num: {this.state.number}</button>
// addEventListener 原生事件调用
return <button id="btn" >Num: {this.state.number}</button>
}
}
function dispatchInteractiveEvent(topLevelType, nativeEvent) {
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
function dispatchEvent(topLevelType, nativeEvent) {
if (!_enabled) {
return;
}
var nativeEventTarget = getEventTarget(nativeEvent);
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
// If we get an event (ex: img onload) before committing that
// component's mount, ignore it for now (that is, treat it as if it was an
// event on a non-React tree). We might also consider queueing events and
// dispatching them after the mount.
targetInst = null;
}
var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
}
function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
if (isBatchingInteractiveUpdates) {
return fn(a, b);
}
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
if (
!isBatchingUpdates &&
!isRendering &&
lowestPriorityPendingInteractiveExpirationTime !== NoWork
) {
// Synchronously flush pending interactive updates.
performWork(lowestPriorityPendingInteractiveExpirationTime, null);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingInteractiveUpdates = true;
isBatchingUpdates = true;
try {
// 这里的 fn 为 dispatchEvent 自定义事件函数
return fn(a, b);
} finally {
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
// 这里的 fn 为setState, 每次setState 都会进入 requestWork
return fn(a);
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
React 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为true, 并且用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1
函数里try { fn } finally { ... }, 这里的 fn 就是setState了,执行栈直接推入 enqueueSetState -> scheduleWork -> requestWork
此时 isBatchingUpdates 为 true, 因此每次调用setState 都不会执行 performSyncWork(),最后finally 里 判断 !isBatchingUpdates && !isRendering
为false,不执行 performSyncWork(), 出栈到 interactiveUpdates$1
函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering
为真(注意这个函数里的previousIsBatchingUpdates为false),因此执行 performSyncWork() 执行更新
多次的 setState 在 enqueueUpdates 函数中,fiber 对象的 baseState 仍然是 0, 但是 fiber 对象上的 updateQueue 更新队列上已经记录好了多次 update 对象将要更新 state 的 payload。
函数调用栈
React 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为 true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1
函数里 try { fn } finally { ... } 这里的 fn 就是setTimeout 为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为 false,不执行 performSyncWork, 出栈到 interactiveUpdates$1
函数里的 try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering
为真(注意这个函数里的 previousIsBatchingUpdates 为 false),执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。
setTimeout 回调属于 marcoTask,它的执行在下一次任务队列开始,在执行 setTimeout 的回调,直接调用 setState,执行栈会推入 enqueueSetState -> scheduleWork -> requestWork
,此时 isBatchingUpdates 为 false(等到setTimeout 执行时 isBatchingUpdates 赋值为 false 了), 因此每走一次 setState 都会走一次 performSyncWork() 将 state 更新。
函数调用栈
baseState
在每次执行完上一次 performWork 后都会更新为最新的 stateReact 的点击事件执行的是 React 包装过的 Event,执行栈:dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates的值(false), 执行 try 里的 fn,执行栈里推入 dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1 函数里try { fn } finally { ... } 这里的 fn 就是setTimeout为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为false,不执行 performSyncWork, 出栈到 interactiveUpdates$1
函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断 !isBatchingUpdates && !isRendering
为真(注意这个函数里的 previousIsBatchingUpdates 为 false),因此执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。
由于 setTimeout() 函数里又使用 batchedUpdates 函数主动包裹一次,因此执行栈里推入setTimeout的回调, 将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录的 原isBatchingUpdates的值(false), 执行 fn 即调用setState,执行栈会推入 enqueueSetState -> scheduleWork -> requestWork
, 在requestWork里判断 isBatchingUpdates 为 true,直接 return 不执行 performSyncWork 函数,执行完三次 setState后,fiber节点上的 updateQueue 的baseState还是 0 ,fisrtUpdate为单链表结构记录着每次setState的payload ;执行完三次 setState 后回到 batchedUpdates$1
函数里try {...} finally { ... } 将 isBatchingUpdates 重新赋值为 false,再走performSyncWork() 将 lastUpdate 的 payload 更新到页面上
函数调用栈
upDateQueue
和方法一直接调用一样原生事件未走自定义事件,因此没有走 dispatchInteractiveEvent -> interactiveUpdates
这个流程, isBatchingUpdates
始终为 false
,因此每次setState
都会走一次 performSyncWork()
函数调用栈
setState
是同步还是异步setState
方法调用时本身是同步的,但调用 setState
不表示 state
是立即更新的, state
的更新是根据我们执行环境的上下文来判断的
如果处于批量更新的情况下 state
就不是立即更新的,如果不处于批量更新情况下有可能立即更新
有可能 是因为现在有 asyncMode
异步渲染的情况,state
也不是立即更新的,需要进入异步调度的过程。
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
let next;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
// 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
next = beginWork(current, workInProgress, nextRenderExpirationTime);
// 将最新的 props 赋值给目前正在用的 props
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
// Record the render duration assuming we didn't bailout (or error).
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
// 更新到最后的节点,叶子节点
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 有兄弟节点返回兄弟节点,否则返回null,往上遍历
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
当一侧的子节点执行 beginWork
完成各个组件的更新,返回它的第一个 child
,当返回 null
时,执行 completeUnitOfWork
beginWork
过程中, 在 update
某个节点时如果报错或者 throw promise
,会被 catch
到,在 throwException
中给节点增加 Incomplete
的 effect
;仍在 renderRoot
的循环中,节点报错,则不会继续渲染它的子节点,直接completeUnitOfWork
,如果 effect
为 Incomplete
,直接走 unwindWork
流程
completeWork
之后首先执行 resetChildExpirationTime
来重置 childExpirationTime
completeWork
之后赋值的 firstEffect
到 lastEffect
链,在 commit
阶段根据这个 effect
链对每个节点执行操作
unwindWork
: 如果 effect
有 shouldCapture
则将 shouldCapture
改成 DidCapture
,并返回节点,返回的是能处理错误的那个节点,跳出 completeUnitOfWork
循环,再继续 beginWork
,此时已经有一个错误的 update
在 updateQueue
中了,在 processUpdateQueue
中会处理 firstCapturedUpdate
,计算出有错误情况下的 state
;否则返回 null
finishClassComponent
中如果非首次渲染并且有 DidCapture
则会调用 forceUnmountCurrentAndReconcile
,进行两次 reconcileChildFibers
function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
// effect of all current children being unmounted; even if the type and key
// are the same, the old child is unmounted and a new child is created.
//
// To do this, we're going to go through the reconcile algorithm twice. In
// the first pass, we schedule a deletion for all the current children by
// passing null.
// nextChildren 为 null, 强制把目前子树节点都删除,渲染出没有子树的 classComponent
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
null,
renderExpirationTime,
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
// the effect of remounting all children regardless of whether their their
// identity matches.
// 老的 children 为 null,清空老的,直接渲染新的 children ,跳过对比 key 的调和过程,提高渲染效率
// nextChildren 已经是根据从错误的 update 里计算出来的state渲染的 children 了
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
}
节点有错误,但不一定有错误处理能力,即在 throwException
中标记 shouldCapture
的节点可能是报错节点的父节点,因此 unwindWork
可能返回为 null
判断是否有兄弟节点来执行不同的操作,如果有兄弟节点则直接返回兄弟节点,如果没有兄弟节点,循环找父节点的兄弟节点,直到 rootFiber
completeUnitOfWork
返回节点跳出循环的情况:
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
// Attempt to complete the current unit of work, then move to the
// next sibling. If there are no more siblings, return to the
// parent fiber.
while (true) {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
// 父节点
const returnFiber = workInProgress.return;
// 兄弟节点
const siblingFiber = workInProgress.sibling;
// Incomplete 为这个节点出现错误并捕获的 effect
// 逻辑与操作判断 workInProgress.effectTag 是否为 �Incomplete
// �Incomplete & �Incomplete => �Incomplete
// 非 �Incomplete & �Incomplete => NoEffect 即没错的情况
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// This fiber completed.
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
if (workInProgress.mode & ProfileMode) {
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
} else {
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
}
// 更新都是从 root 节点开始,需要同步子树中更新优先级最高的 expirationTime 到 root 上
// 完成一次节点更新后,需要更新 childExpirationTime
// 重置 childExpirationTime (子节点当中更新优先级最高的对应的 expirationTime )
stopWorkTimer(workInProgress);
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
// 没有错误才会更新, 有错误的不会渲染子节点的
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
// 处理父节点的 effect
// 真正要做的是把当前节点的 firstEffect 到 lastEffect (即当前节点的子节点的 effect ) 单向链表挂载到它的父节点的同样的位置
// 表示 returnFiber 还未记录任何有副作用(effect)的子节点
if (returnFiber.firstEffect === null) {
// 当前节点的 firstEffect 赋值给 returnFiber
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
// 如果 returnFiber 已经有记录过别的 side effect 节点,当前节点的 firstEffect 则挂载在单向链表的最后
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
// 更新 lastEffect
returnFiber.lastEffect = workInProgress.lastEffect;
}
// 处理当前节点的 effect
// 当前节点也可能有 effect
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
// 如果当前节点有 effect 且大于 1, PerformeWork 值为 1 给 devtool 用的,则将当前节点挂载到父节点的 side effect 链的最后一个
if (effectTag > PerformedWork) {
// 如果 returnFiber 有记录过别的 effect
if (returnFiber.lastEffect !== null) {
// 更新最后一个的 next
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
// 否则为第一个 effect
returnFiber.firstEffect = workInProgress;
}
// 更新 lastEffect
returnFiber.lastEffect = workInProgress;
}
// 一层一层往上,最终在 RootFiber 上赋值的 firstEffect 到 lastEffect 这个单向链表结构记录了整个应用的当中所有需要更新的 dom节点/组件对应的 fiber 对象 ,以最小程度更新应用
}
// 如果有兄弟节点则返回兄弟节点,跳出这个循环,继续 beginWork
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
// 如果没有兄弟节点,循环找父节点的兄弟节点
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
// 如果都没有,则到达 rootFiber 顶点了,更新阶段完成,进入 commitRoot 提交阶段
// We've reached the root.
return null;
}
} else {
// Incomplete 抛出过错误的情况, workInProgress 为报错的那个组件, 并不一定是能处理错误的那个组件,可能不是 classComponent 或者 没有错误处理的能力
// 可以处理错误的组件才会标记 shouldCapture,可能是父组件
// 即 unwindWork 执行完后 next 可能为 null 的情况
if (workInProgress.mode & ProfileMode) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
// workInProgress 的 effect 如果为 shouldCapture ,那么返回 workInProgress ,并且将 shouldCapture 改成 DidCapture
// 否则返回 null
const next = unwindWork(workInProgress, nextRenderExpirationTime);
// Because this fiber did not complete, don't reset its expiration time.
if (workInProgress.effectTag & DidCapture) {
// Restarting an error boundary
stopFailedWorkTimer(workInProgress);
} else {
stopWorkTimer(workInProgress);
}
// next 不为 null 即 workInProgress 是有 DidCapture 的 effectTag
if (next !== null) {
stopWorkTimer(workInProgress);
if (enableProfilerTimer) {
// Include the time spent working on failed children before continuing.
if (next.mode & ProfileMode) {
let actualDuration = next.actualDuration;
let child = next.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
next.actualDuration = actualDuration;
}
}
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// 将 effectTag 变成只有 HostEffectMask 上所具有的 effect
// shouldCapture、Incomplete 会被去掉
next.effectTag &= HostEffectMask;
// 返回的是能处理错误的那个节点,再继续 beginWork,此时会根据是否有 DidCapture 在调和子组件中做不同操作。
return next;
}
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
// 清空父链的 effect 链
returnFiber.firstEffect = returnFiber.lastEffect = null;
// 报错组件没有错误处理能力时,需要在父组件上增加 Incomplete,
// 即子树中的组件如果报错,那对于父链上所有组件的 completeUnitOfWork 都会执行 unwindWork
returnFiber.effectTag |= Incomplete;
}
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
return null;
}
}
}
return null;
}
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010;
export const Update = /* */ 0b00000000100;
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Union of all host effects
// HostEffectMask 为以上 effect 的并集,Incomplete、ShouldCapture 为 HostEffectMask 的补集
export const HostEffectMask = /* */ 0b00111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
pop
出各种 context
相关的内容HostComponent
执行初始化HostComponent、HostText、SuspenseComponent
有操作普通 dom
节点,如 div、span
diffProperties
计算需要更新的内容,以此进行对比来判断一个节点是否真的需要更新dom property
,不同的 property
处理方式不同createInstance
创建 dom
,并在 dom
对象上记录对应的 fiber
节点 和 props
appendAllChildren
构建 dom
树,从底往上只 append
一层 child
及 child.sibling
finalizeInitialChildren
初始化 dom
节点属性、初始化事件体系dom
节点记录在 workInProgress.stateNode
属性上, 在 appendAllChildren
中读取function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
// ...
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
// current !== null => 非首次渲染
// workInProgress.stateNode != null => 节点被创建过(挂载)
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
// 首次渲染
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
break;
}
const currentHostContext = getHostContext();
// 服务端渲染相关
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
} else {
// 创建 dom instance
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// append 子节点
appendAllChildren(instance, workInProgress, false, false);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
// 初始化 react 事件体系,初始化 DOM 节点属性
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
// 节点需要 autofocus 时才mark update
markUpdate(workInProgress);
}
// 将 dom节点 记录在 stateNode 属性上, 在 appendAllChildren 中读取
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
break;
}
}
workInProgress.type
创建 dom
dom
对象上记录对应的 fiber
和 props
function createInstance(
type: string,
props: Props, // newProps
rootContainerInstance: Container, //
hostContext: HostContext,
internalInstanceHandle: Object, // workInProgress
): Instance {
let parentNamespace: string;
if (__DEV__) {
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
// 创建 dom Element
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
// cache Fiber 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
precacheFiberNode(internalInstanceHandle, domElement);
// cache props 将 newProps 存储在 domElement 的 internalEventHandlersKey 属性上,props 会有 dom 的 attr 相关的属性
updateFiberProps(domElement, props);
return domElement;
}
// createElement 创建 dom
function createElement(
type: string,
props: Object,
rootContainerElement: Element | Document,
parentNamespace: string,
): Element {
let isCustomComponentTag;
// Document 对象
const ownerDocument: Document = getOwnerDocumentFromRootContainer(
rootContainerElement,
);
let domElement: Element;
let namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
if (__DEV__) {}
// 特殊节点处理,如果直接创建 script 标签,会自动执行里面的脚本
// 通过先插入,后删除的方式获取 script 标签对应的 dom
if (type === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
const div = ownerDocument.createElement('div');
div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
// This is guaranteed to yield a script element.
const firstChild = ((div.firstChild: any): HTMLScriptElement);
domElement = div.removeChild(firstChild);
} else if (typeof props.is === 'string') {
domElement = ownerDocument.createElement(type, {is: props.is});
} else {
// 原生 dom 创建元素
domElement = ownerDocument.createElement(type);
// select 标签设置属性
if (type === 'select' && props.multiple) {
const node = ((domElement: any): HTMLSelectElement);
node.multiple = true;
}
}
} else {
// 不是 html 的 nameSpace
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
// 返回 domElement
return domElement;
}
dom
树,从底往上 append
子节点,只会 append
所有子树里第一层 dom/text
节点 及其对应的 sibling
,不会 append
child.child
中的 dom/text
节点child
节点在执行 completeUnitOfWork
会 append
自己的 child
和 child.sibling
appendAllChildren = function(
parent: Instance, // 创建的 dom
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
// 当前节点的子节点
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
// 子节点是 原生 dom 节点 或者纯文本节点
// 直接调用原生 dom 的 appendChild
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 子节点不是 dom 标签或者纯文本节点, 且 child.child 不为null
node.child.return = node;
// 重新赋值 node 为 node.child,并跳过此次循环,进入下一次循环,一直往下找直到找到 dom 节点 或者 纯文本节点
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 这个 while 循环目的是找到有兄弟节点的父节点,如果父节点有兄弟节点,则跳出这个小循环,会去遍历其兄弟节点
// 如果没有兄弟节点,继续往上找最终会回到 node.return === workInProgress 跳出循环
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// 将兄弟节点的父节点都指向同一个
node.sibling.return = node.return;
// 有兄弟节点继续遍历
node = node.sibling;
}
};
trapBubbledEvent
绑定不同事件setInitialDOMProperties
初始化 dom
节点属性、和事件监听
CSSPropertyOperations.setValueForStyles
初始化样式DOMPropertyOperations.setValueForProperty
初始化 dom
属性,会对 nextProp
做一些判断,要忽略的属性直接 return
,要删除的属性,将通过 node.removeAttribute
删除,否则通过 node.setAttribute
添加function finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
// 节点需要 autofocus 时才 mark update
return shouldAutoFocusHostComponent(type, props);
}
// setInitialProperties
function setInitialProperties(
domElement: Element,
tag: string,
rawProps: Object,
rootContainerElement: Element | Document,
): void {
const isCustomComponentTag = isCustomComponent(tag, rawProps);
if (__DEV__) {}
// TODO: Make sure that we check isMounted before firing any of these events.
// trapBubbledEvent 绑定不同事件
let props: Object;
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
trapBubbledEvent(TOP_ERROR, domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'form':
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
props = rawProps;
break;
case 'details':
trapBubbledEvent(TOP_TOGGLE, domElement);
props = rawProps;
break;
// input option select textarea 为可交互输入的组件
case 'input':
ReactDOMInput.initWrapperState(domElement, rawProps);
props = ReactDOMInput.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'option':
ReactDOMOption.validateProps(domElement, rawProps);
props = ReactDOMOption.getHostProps(domElement, rawProps);
break;
case 'select':
ReactDOMSelect.initWrapperState(domElement, rawProps);
props = ReactDOMSelect.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMTextarea.initWrapperState(domElement, rawProps);
props = ReactDOMTextarea.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
default:
props = rawProps;
}
// 常用 props 错误提醒
assertValidProps(tag, props);
// 初始化 DOM 节点属性
setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
props,
isCustomComponentTag,
);
// ...
}
// setInitialDOMProperties
function setInitialDOMProperties(
tag: string,
domElement: Element,
rootContainerElement: Element | Document,
nextProps: Object,
isCustomComponentTag: boolean,
): void {
for (const propKey in nextProps) {
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
// style 属性
if (__DEV__) {
if (nextProp) {
// Freeze the next style object so that we can assume it won't be
// mutated. We have already warned for this in the past.
Object.freeze(nextProp);
}
}
// Relies on `updateStylesByID` not mutating `styleUpdates`.
// 设置样式,对于 width 类属性,自动加 px
CSSPropertyOperations.setValueForStyles(domElement, nextProp);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 文本属性,直接用原生 node.innerHTML = html 设置
const nextHtml = nextProp ? nextProp[HTML] : undefined;
if (nextHtml != null) {
setInnerHTML(domElement, nextHtml);
}
} else if (propKey === CHILDREN) {
// 子节点,这里只会是文本或数字了,dom 节点或者组件已经在 appendAllChildren 操作完了
if (typeof nextProp === 'string') {
// Avoid setting initial textContent when the text is empty. In IE11 setting
// textContent on a <textarea> will cause the placeholder to not
// show within the <textarea> until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
const canSetTextContent = tag !== 'textarea' || nextProp !== '';
if (canSetTextContent) {
setTextContent(domElement, nextProp);
}
} else if (typeof nextProp === 'number') {
setTextContent(domElement, '' + nextProp);
}
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// We polyfill it separately on the client during commit.
// We could have excluded it in the property list instead of
// adding a special case here, but then it wouldn't be emitted
// on server rendering (but we *do* want to emit it in SSR).
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// 事件相关
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 事件监听
ensureListeningTo(rootContainerElement, propKey);
}
} else if (nextProp != null) {
// 最终 node.setAttribute 设置属性,或者 removeAttribute 删除不必要的属性
DOMPropertyOperations.setValueForProperty(
domElement,
propKey,
nextProp,
isCustomComponentTag,
);
}
}
}
props
里对应 dom
的 property
的对比是否有变化来进行不同的操作updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container,
) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// 新老 props 相同,直接 return
if (oldProps === newProps) {
return;
}
// 当前节点对应的 DOM 节点
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
// updatePayload 为调用 diffProperties 返回的结果
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
// 如果有 updatePayload 则要标记更新,对于 input,option, select, textarea 默认为空数组,即便是 props 没有更新的内容,仍旧需要标记 update
if (updatePayload) {
markUpdate(workInProgress);
}
};
updatePayload
赋值给 workInProgress.updateQueue
updatePayload
存在,则标记 effect
为 update
老 props
循环,如果新 props
没有而老 props
有则要删除新 props
循环,如果新 props
有这个 key
,且新老 props[key]
值不想等则将这个 key
标记更新updatePayload
结构为 [k1, v1, k2, v2, k3, v3]
\function diffProperties(
domElement: Element,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
rootContainerElement: Element | Document,
): null | Array<mixed> {
if (__DEV__) {
validatePropertiesInDevelopment(tag, nextRawProps);
}
let updatePayload: null | Array<any> = null;
let lastProps: Object;
let nextProps: Object;
// 特殊标签
switch (tag) {
case 'input':
lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'option':
lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'select':
lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'textarea':
lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
default:
lastProps = lastRawProps;
nextProps = nextRawProps;
if (
typeof lastProps.onClick !== 'function' &&
typeof nextProps.onClick === 'function'
) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
break;
}
// props 错误提醒
assertValidProps(tag, nextProps);
let propKey;
let styleName;
let styleUpdates = null;
// 循环老的 props,如果新 props 没有而老 props 有则要删除(style属性:将value置为 ’‘,其他属性置为 null)这个属性
for (propKey in lastProps) {
if (
// 新的有
nextProps.hasOwnProperty(propKey) ||
// 老的没有
!lastProps.hasOwnProperty(propKey) ||
// 老的为 null
lastProps[propKey] == null
) {
// 如果符合这个条件则跳过,否则是新的没有且老的有,那么就要删除
continue;
}
// 不符合以上条件则是删除操作
if (propKey === STYLE) {
const lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
// 设置删除老的 style props
styleUpdates[styleName] = '';
}
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
// Noop. This is handled by the clear text mechanism.
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// Noop. It doesn't work on updates anyway.
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
// that the "current" fiber pointer gets updated so we need a commit
// to update this element.
if (!updatePayload) {
updatePayload = [];
}
} else {
// For all other deleted properties we add it to the queue. We use
// the whitelist in the commit phase instead.
// 要删除的属性,则 value 都设置为 null
(updatePayload = updatePayload || []).push(propKey, null);
}
}
// 循环新的 props,如果新 props 有这个 key,且新老 props[key] 值不想等则将这个 key 标记更新( style属性则为直接设置 style[key] = newValue ),
for (propKey in nextProps) {
// newValue
const nextProp = nextProps[propKey];
// oldValue
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (
// 自身没有这个 key
!nextProps.hasOwnProperty(propKey) ||
// 新老相等
nextProp === lastProp ||
// 均为 null
(nextProp == null && lastProp == null)
) {
// 符合以上条件则跳过, 否则就要加入 updatePayload 中
continue;
}
// 对 style 属性的操作
if (propKey === STYLE) {
if (__DEV__) {
if (nextProp) {
// Freeze the next style object so that we can assume it won't be
// mutated. We have already warned for this in the past.
Object.freeze(nextProp);
}
}
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
for (styleName in lastProp) {
if (
// style 在老的 props 中,但不在新的 props 中,则属性对应的 value 设置为 ‘’(对应删除)
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
// 如果新props 有且新老prop 值不想等,赋值 styleUpdates[styleName] 为新的值
if (
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
// 没有老 props 直接添加
updatePayload.push(propKey, styleUpdates);
}
styleUpdates = nextProp;
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
const nextHtml = nextProp ? nextProp[HTML] : undefined;
const lastHtml = lastProp ? lastProp[HTML] : undefined;
if (nextHtml != null) {
if (lastHtml !== nextHtml) {
(updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
}
} else {
// TODO: It might be too late to clear this if we have children
// inserted already.
}
} else if (propKey === CHILDREN) {
// 子节点,文本类型的,不相同直接 push
if (
lastProp !== nextProp &&
(typeof nextProp === 'string' || typeof nextProp === 'number')
) {
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
// We eagerly listen to this even though we haven't committed yet.
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 事件相关
ensureListeningTo(rootContainerElement, propKey);
}
if (!updatePayload && lastProp !== nextProp) {
// This is a special case. If any listener updates we need to ensure
// that the "current" props pointer gets updated so we need a commit
// to update this element.
updatePayload = [];
}
} else {
// For any other property we always add it to the queue and then we
// filter it out using the whitelist during the commit.
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
if (styleUpdates) {
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
// updatePayload 的格式为 [k1, v1, k2, v2, k3, v3]
return updatePayload;
}
case HostText: {
let newText = newProps;
// 更新渲染
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
// 新老text不同则标记更新
updateHostText(current, workInProgress, oldText, newText);
} else {
// 首次渲染
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
// 创建 textInstance
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
break;
}
// 判断不相同,则标记 update
updateHostText = function(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
// If the text differs, mark it as an update. All the work in done in commitWork.
if (oldText !== newText) {
markUpdate(workInProgress);
}
};
// createTextInstance 创建 textInstance
function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
}
// 调用 document.createTextNode
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
// cache Fiber, 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
childExpirationTime
childExpirationTime
是用来记录某一个节点子树当中优先级最高对应的那个 expirationTime
root
节点开始,需要同步子树中更新优先级最高的 expirationTime
到 root
上completeWork
完成节点更新后执行,重置 childExpirationTime
(子节点当中更新优先级最高的对应的 expirationTime
)exprationTime
, 只能通过遍历所有的第一层的子节点的 expirationTime
以及每个子节点的 childExpirationTime
, completeUnitOfWork
是由低往上更新的过程,每个节点都会对应这个操作,因此每个节点都会更新到优先级最高的 expirationTime
。对比 appendChild
时的操作类似,都只会 appendChild
第一层子节点的 dom
。function resetChildExpirationTime(
workInProgress: Fiber,
renderTime: ExpirationTime,
) {
// renderTime !== Never => 当前任务执行更新
// workInProgress.childExpirationTime === Never => 当前节点的子节点没有更新
// 直接返回
if (renderTime !== Never && workInProgress.childExpirationTime === Never) {
// The children of this component are hidden. Don't bubble their
// expiration times.
return;
}
let newChildExpirationTime = NoWork;
// Bubble up the earliest expiration time.
if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// ....
} else {
let child = workInProgress.child;
while (child !== null) {
// 子节点的 expirationTime
const childUpdateExpirationTime = child.expirationTime;
// 子节点的子树中更新优先级最高的那个 expirationTime
const childChildExpirationTime = child.childExpirationTime;
if (
newChildExpirationTime === NoWork ||
(childUpdateExpirationTime !== NoWork && // 找到不为 NoWork且优先级最高的子节点对应的 expirationTime
childUpdateExpirationTime < newChildExpirationTime)
) {
newChildExpirationTime = childUpdateExpirationTime;
}
if (
newChildExpirationTime === NoWork ||
(childChildExpirationTime !== NoWork && // 找到子节点子树中优先级不为 NoWork 且更新优先级最高的 expirationTime
childChildExpirationTime < newChildExpirationTime)
) {
newChildExpirationTime = childChildExpirationTime;
}
// 横向查找(有兄弟节点的情况才会要去找更新优先级最高的 expirationTime)
child = child.sibling;
}
}
// 更新当前节点的 childExpirationTime
workInProgress.childExpirationTime = newChildExpirationTime;
}
Incomplete
的 effect
,在 completeUnitOfWork
时会判断存在 Incomplete
则调用 unwindWork
error boundary
的节点增加 DidCapture
的 effect
// renderRoot
do {
try {
workLoop(isYieldy);
} catch (thrownValue) {
// 错误处理核心点:
// 给报错的节点增加 Incomplete 副作用,在 completeUnitOfWork 时会判断存在 Incomplete 则调用 unwindWork, 不存在则调用 completeWork
// 给父链上具有 error boundary 的节点增加副作用
// 创建错误相关的更新
// 非正常流程,不应该为 null
if (nextUnitOfWork === null) {
// This is a fatal error.
// 致命错误,直接中断渲染流程
didFatal = true;
// 设置 nextFlushedRoot.expirationTime = NoWork;
onUncaughtError(thrownValue);
} else {
if (__DEV__) {
// Reset global debug state
// We assume this is defined in DEV
(resetCurrentlyProcessingQueue: any)();
}
const failedUnitOfWork: Fiber = nextUnitOfWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
}
// TODO: we already know this isn't true in some cases.
// At least this shows a nicer error message until we figure out the cause.
// https://github.com/facebook/react/issues/12449#issuecomment-386727431
invariant(
nextUnitOfWork !== null,
'Failed to replay rendering after an error. This ' +
'is likely caused by a bug in React. Please file an issue ' +
'with a reproducing case to help us find it.',
);
const sourceFiber: Fiber = nextUnitOfWork;
let returnFiber = sourceFiber.return;
// 在更新 rootFiber 时出现致命错误
if (returnFiber === null) {
// This is the root. The root could capture its own errors. However,
// we don't know if it errors before or after we pushed the host
// context. This information is needed to avoid a stack mismatch.
// Because we're not sure, treat this as a fatal error. We could track
// which phase it fails in, but doesn't seem worth it. At least
// for now.
didFatal = true;
onUncaughtError(thrownValue);
} else {
// 常规错误处理, 给报错节点增加 Incomplete 的 effect
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
nextRenderExpirationTime,
);
// 节点报错,则不会继续渲染它的子节点,直接 completeUnitOfWork ,直接走 unwindWork 流程
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
}
}
}
break;
} while (true);
nextFlushedRoot.expirationTime = NoWork
取消当前 root
的更新onUncaughtError(error: mixed) {
nextFlushedRoot.expirationTime = NoWork;
if (!hasUnhandledError) {
hasUnhandledError = true;
unhandledError = error;
}
}
Incomplete
的 effect
firstEffect、lastEffect
链suspened
异步组件抛出的 promise
classComponent
,如果没有,那么会在 hostRoot
上进行内置处理, root
节点肯定会标记 ShouldCapture
classComponent
都会标记 effect
为 ShouldCapture
firstCapturedUpdate、lastCapturedUpdate链
加入到 updateQueue
中getDerivedStateFromError
是在 update.payload
函数里执行的componentDidCatch
是在 update.callback
里执行的function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed, // 抛出来的错误 value
renderExpirationTime: ExpirationTime,
) {
// The source fiber did not complete.
sourceFiber.effectTag |= Incomplete;
// Its effect list is no longer valid.
// 清空 effect 链
sourceFiber.firstEffect = sourceFiber.lastEffect = null;
// throw 的是 promise 的情况,suspense 组件
if (
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) { // ...}
// We didn't find a boundary that could handle this type of exception. Start
// over and traverse parent path again, this time treating the exception
// as an error.
// nextRenderDidError = true 标记错误变量
renderDidError();
// 处理过的错误信息, 类似 Error 对象,有错误产生的文件、方法、行数等信息
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
// 如果组件没有 getDerivedStateFromError 或 componentDidCatch,则是没有错误处理功能的
// 需要往上找找到第一个可以处理错误的 classComponent 来进行错误的 update 的创建,并加入到 updateQueue 中,在 commit 阶段进行调用
// 如果找不到可以进行错误处理的 classComponent,那么会在 hostRoot 上进行处理,算是一个内置的错误处理的方式,也会创建对应的错误的 update
do {
switch (workInProgress.tag) {
case HostRoot: {
const errorInfo = value;
// 标记 effect
workInProgress.effectTag |= ShouldCapture;
workInProgress.expirationTime = renderExpirationTime;
// 类似 setState 的创建一个错误处理的 update,创建 firstCapturedUpdate、lastCapturedUpdate链 , 创建 root 节点的错误更新
const update = createRootErrorUpdate(
workInProgress,
errorInfo,
renderExpirationTime,
);
// 类似 enqueueUpdate 的推入 updateQueue
enqueueCapturedUpdate(workInProgress, update);
return;
}
case ClassComponent:
// Capture and retry
const errorInfo = value;
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
// 没有 DidCapture
(workInProgress.effectTag & DidCapture) === NoEffect &&
// 并且有 静态方法 getDerivedStateFromError 或者 实例的 componentDidCatch 错误处理函数
(typeof ctor.getDerivedStateFromError === 'function' ||
(instance !== null &&
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
// 标记 effect 为 ShouldCapture
workInProgress.effectTag |= ShouldCapture;
workInProgress.expirationTime = renderExpirationTime;
// Schedule the error boundary to re-render using updated state
// 类似 setState 的创建一个错误处理的 update, 创建 firstCapturedUpdate、lastCapturedUpdate链
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
renderExpirationTime,
);
// 加入到 updateQueue 中
enqueueCapturedUpdate(workInProgress, update);
return;
}
break;
default:
break;
}
// 往上遍历
workInProgress = workInProgress.return;
} while (workInProgress !== null);
}
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<mixed> {
const update = createUpdate(expirationTime);
// Unmount the root by rendering null.
// 对比 setState 的 tag 为 UpdateState, 这里为捕获错误更新
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
// rootFiber 的 payload 为 element 属性的对象,这里置为 null,不渲染任何子节点
update.payload = {element: null};
const error = errorInfo.value;
update.callback = () => {
onUncaughtError(error);
logError(fiber, errorInfo);
};
return update;
}
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
expirationTime: ExpirationTime,
): Update<mixed> {
// 创建 update
const update = createUpdate(expirationTime);
// 标记为捕获错误更新
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
// 如果有 getDerivedStateFromError 则将其返回结果设置为 payload 回调更新为 state
if (typeof getDerivedStateFromError === 'function') {
const error = errorInfo.value;
update.payload = () => {
return getDerivedStateFromError(error);
};
}
const inst = fiber.stateNode;
// 如果有 componentDidCatch 则加入到 callback 中,等到组件更新完后才调用
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
const error = errorInfo.value;
const stack = errorInfo.stack;
logError(fiber, errorInfo);
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
};
}
return update;
}
completeWork
对不同类型组件进行处理effect
为 shouldCapture
的组件设置 DidCapture
副作用ClassComponent、HostRoot、SuspenseComponent
操作,其他类型组件只是 pop context
classComponent、SuspenseComponent
如果有 shouldCapture
的 effectTag
则会返回 workInProgress
,否则返回 null
HostRoot
总会返回 workInProgress
,其他类型都返回 null
completeWork
的区别在于会判断 shouldCapture
,如果有 shouldCapture
,那么去掉 shouldCapture
并增加 DidCapture
的 effectTag
function unwindWork(
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
switch (workInProgress.tag) {
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
const effectTag = workInProgress.effectTag;
// 如果有 shouldCapture
if (effectTag & ShouldCapture) {
// 去掉 shouldCapture 增加 DidCapture
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const effectTag = workInProgress.effectTag;
invariant(
(effectTag & DidCapture) === NoEffect,
'The root failed to unmount after an error. This is likely a bug in ' +
'React. Please file an issue.',
);
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
return workInProgress;
}
case HostComponent: {
popHostContext(workInProgress);
return null;
}
case SuspenseComponent: {
const effectTag = workInProgress.effectTag;
if (effectTag & ShouldCapture) {
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
// Captured a suspense effect. Set the boundary's `alreadyCaptured`
// state to true so we know to render the fallback.
const current = workInProgress.alternate;
const currentState: SuspenseState | null =
current !== null ? current.memoizedState : null;
let nextState: SuspenseState | null = workInProgress.memoizedState;
if (nextState === null) {
// No existing state. Create a new object.
nextState = {
alreadyCaptured: true,
didTimeout: false,
timedOutAt: NoWork,
};
} else if (currentState === nextState) {
// There is an existing state but it's the same as the current tree's.
// Clone the object.
nextState = {
alreadyCaptured: true,
didTimeout: nextState.didTimeout,
timedOutAt: nextState.timedOutAt,
};
} else {
// Already have a clone, so it's safe to mutate.
nextState.alreadyCaptured = true;
}
workInProgress.memoizedState = nextState;
// Re-render the boundary.
return workInProgress;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
return null;
case ContextProvider:
popProvider(workInProgress);
return null;
default:
return null;
}
}
createPortal
创建 portal
,返回一个类似 reactElement
的对象function createPortal(
children: ReactNodeList,
container: DOMContainer,
key: ?string = null,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
// TODO: pass ReactDOM portal implementation as third argument
return ReactPortal.createPortal(children, container, null, key);
}
function createPortal(
children: ReactNodeList,
containerInfo: any,
// TODO: figure out the API for cross-renderer implementation.
implementation: any,
key: ?string = null,
): ReactPortal {
return {
// This tag allow us to uniquely identify this as a React Portal
$$typeof: REACT_PORTAL_TYPE,
key: key == null ? null : '' + key,
children,
containerInfo,
implementation,
};
}
portal
类型组件创建 fiber
节点// reconcileChildFibers
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// ....
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// ....
}
// reconcileSinglePortal
function reconcileSinglePortal(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
expirationTime: ExpirationTime,
): Fiber {
const key = portal.key;
// 原有的子节点
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (
child.tag === HostPortal &&
// 原有子节点的 container 与新的 portal<createPortal返回的那个对象> 相同,说明可复用
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
// 删除兄弟节点
deleteRemainingChildren(returnFiber, child.sibling);
// 复用当前节点
const existing = useFiber(
child,
portal.children || [],
expirationTime,
);
existing.return = returnFiber;
return existing;
} else {
// 不能复用删除所有
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// key 不相同 删除所有
deleteChild(returnFiber, child);
}
// 循环找知道找到可复用的节点为止
child = child.sibling;
}
// 如果没有可以复用的,那么要创建新的 fiber
const created = createFiberFromPortal(
portal,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
function createFiberFromPortal(
portal: ReactPortal,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
// 将 portal.children 直接赋值为 pendingProps
const pendingProps = portal.children !== null ? portal.children : [];
// 创建 fiber, tag 为 HostPortal
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
fiber.expirationTime = expirationTime;
// 因为 portal 有单独的挂载点,需要手动添加 stateNode
// 参考 RootFiber.stateNode === FiberRoot
fiber.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null, // Used by persistent updates
implementation: portal.implementation,
};
return fiber;
}
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
// 有单独的挂载点,container 需要 push
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
// 获取新的 children
const nextChildren = workInProgress.pendingProps;
// 首次调用
if (current === null) {
// Portals are special because we don't append the children during mount
// but at commit. Therefore we need to track insertions which the normal
// flow doesn't do during mount. This doesn't happen at the root because
// the root always starts with a "current" with a null child.
// TODO: Consider unifying this with how the root works.
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// 相当于 mountChildFibers
// 更新渲染
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}
return workInProgress.child;
}
forwardRef
用来传递 ref
fiber
是在 reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps
中将 fiberTag
标记为 ForwardRef
function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
if (__DEV__) {
if (typeof render !== 'function') {
warningWithoutStack(
false,
'forwardRef requires a render function but was given %s.',
render === null ? 'null' : typeof render,
);
} else {
warningWithoutStack(
// Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
render.length === 0 || render.length === 2,
'forwardRef render functions accept exactly two parameters: props and ref. %s',
render.length === 1
? 'Did you forget to use the ref parameter?'
: 'Any additional parameter will be undefined.',
);
}
if (render != null) {
warningWithoutStack(
render.defaultProps == null && render.propTypes == null,
'forwardRef render functions do not support propTypes or defaultProps. ' +
'Did you accidentally pass a React component?',
);
}
}
return {
$$typeof: REACT_FORWARD_REF_TYPE,
render, // render 即 function component
};
}
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
// updateForwardRef
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
type: any,
nextProps: any,
renderExpirationTime: ExpirationTime,
) {
// render 即 React.forwardRef 传入的 function component
const render = type.render;
// 调用组件产生的 ref
const ref = workInProgress.ref;
if (hasLegacyContextChanged()) {
// Normally we can bail out on props equality but if context has changed
// we don't do the bailout and we have to reuse existing props instead.
} else if (workInProgress.memoizedProps === nextProps) {
// 之前的 ref
const currentRef = current !== null ? current.ref : null;
// 两个ref 相同则跳过更新
if (ref === currentRef) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
let nextChildren;
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
ReactCurrentFiber.setCurrentPhase('render');
nextChildren = render(nextProps, ref);
ReactCurrentFiber.setCurrentPhase(null);
} else {
// React.forwardRef((props, ref) => {})
// 传递 ref 并获取新 children,没有传 context
nextChildren = render(nextProps, ref);
}
// 调和子节点
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
<ConCurrentmode />
和 <StrictMode />
Fiber
对象上记录所有子节点所对应的渲染模式,在创建更新时候根据 mode
创建 expirationTime
export type TypeOfMode = number;
export const NoContext = 0b000;
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
createFiberFromElement -> createFiberFromTypeAndProps
中为 mode
组件创建 fiber
节点,这里的传入的 mode
为 returnFiber(即父组件)
上的 mode
,而父组件上的 mode
最终来自 hostRootFiber
上,默认为 NoContext
Mode组件
它会有一个特殊的的 Fiber
对象上的 mode
属性
conCurrentMode
传入的 mode
为 NoContext | ConcurrentMode | StrictMode -> 0b011
StrictMode
传入的 Mode
为 NoContext | StrictMode -> 0b010
Mode 组件
的子节点过程中,传入的 mode
则是这个 Mode 组件
它 fiber
对象上的 mode
属性,对于 Mode 组件
它所有子树上的子节点都会具有这个 Mode 组件
它 fiber
对象上的 mode
属性,以此来记录这个子树处于那个渲染模式下,才会有后期创建更新时根据 mode
属性如 ConcurrentMode
会对 expirationTime
有个特殊的计算过程//
function createHostRootFiber(isConcurrent: boolean): Fiber {
// 这里在调用 ReactDOM.render 方法时 isConcurrent 为false,所以传递到子节点一直都是 NoContext
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
// reconcileChildFibers
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// ...
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
// ...
}
// reconcileSingleElement
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
// ....
// 创建新的节点
if (element.type === REACT_FRAGMENT_TYPE) {
// Fragment 传的elements传入的是 element.props.children, --> createFiber接收的 penddingProps 即这个 children
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 根据 element type来创建不同 fiber 对象
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
// createFiberFromTypeAndProps
function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
) {
let fiber;
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
// component 组件
fiberTag = ClassComponent;
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
// concurrent mode
// NoContext | ConcurrentMode | StrictMode -> 0b011
case REACT_CONCURRENT_MODE_TYPE:
return createFiberFromMode(
pendingProps,
mode | ConcurrentMode | StrictMode,
expirationTime,
key,
);
// strict mode
// NoContext | StrictMode -> 0b010
case REACT_STRICT_MODE_TYPE:
return createFiberFromMode(
pendingProps,
mode | StrictMode,
expirationTime,
key,
);
// ....
return fiber;
}
type
赋值:mode & ConcurrentMode
等于 NoContext
则为 StrictMode
,否则为 ConCurrentMode
mode
对应的 type
function createFiberFromMode(
pendingProps: any,
mode: TypeOfMode,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
const fiber = createFiber(Mode, pendingProps, key, mode);
// TODO: The Mode fiber shouldn't have a type. It has a tag.
// 0b011 & 0b001 => 0b001 => ConCurrentMode
// 0b010 & 0b001 => 0b000 => StrictMode
const type =
(mode & ConcurrentMode) === NoContext
? REACT_STRICT_MODE_TYPE
: REACT_CONCURRENT_MODE_TYPE;
fiber.elementType = type;
fiber.type = type;
fiber.expirationTime = expirationTime;
return fiber;
}
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const nextChildren = workInProgress.pendingProps.children;
// children 来源?
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
React.memo(com, compare)
创建一个具有 pureComponent
特性的 functionComponent
$$typeof
标记 fiberTag
为 MemoComponent
function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
return {
$$typeof: REACT_MEMO_TYPE,
// 这个 type 即传入进来的 function Component
type,
compare: compare === undefined ? null : compare,
};
}
// function createFiberFromTypeAndProps
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
memo
组件的 props
都是作用于传入的 function
组件内,memo
组件的意义在于进行一次组件包裹,可传入自定义比较函数child
没有使用调和子节点方式,而是直接创建compare
函数如果没传入,则使用 shallowEqual
,返回 true
则跳过更新。case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
updateExpirationTime,
renderExpirationTime: ExpirationTime,
): null | Fiber {
// 首次渲染
if (current === null) {
// 拿到传入的那个 function component
let type = Component.type;
// 如果是纯函数组件没有 contruct、defaultProps 的组件且没有比较函数,则按 SimpleMemoComponent 更新
if (isSimpleFunctionComponent(type) && Component.compare === null) {
// If this is a plain function component without default props,
// and with only the default shallow comparison, we upgrade it
// to a SimpleMemoComponent to allow fast path updates.
// 更新 tag,下次更新直接走 updateSimpleMemoComponent
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = type;
return updateSimpleMemoComponent(
current,
workInProgress,
type,
nextProps,
updateExpirationTime,
renderExpirationTime,
);
}
// reconcileChildFibers 会把 props 用于当前组件本身的 children 调和的过程
// 而对于 memo 组件的 child 来自于传入的那个 function component ,因此要将 props 传递给 function component,来创建 children
// 直接创建,而不是调和子节点,
let child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
null,
workInProgress.mode,
renderExpirationTime,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
return child;
}
let currentChild = ((current.child: any): Fiber); // This is always exactly one child
// 如果没有必要更新
if (
updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime
) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
// compare 有则用,否则为 shallowEqual
compare = compare !== null ? compare : shallowEqual;
// 相同则跳过更新
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
let newChild = createWorkInProgress(
currentChild,
nextProps,
renderExpirationTime,
);
newChild.ref = workInProgress.ref;
newChild.return = workInProgress;
workInProgress.child = newChild;
return newChild;
}
function updateSimpleMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any, // memo 传入的 function component
nextProps: any,
updateExpirationTime,
renderExpirationTime: ExpirationTime,
): null | Fiber {
if (
// 更新渲染
current !== null &&
// 优先级比当前低 或者 不需要更新
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {
const prevProps = current.memoizedProps;
// 如果 props 浅相同 并且 ref 相同 则跳过更新
if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref
) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// 如果需要更新,纯 function component 按 function Component 方式更新
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
FiberRoot
是 ReactRoot
生成实例时调用 ReactFiberReconciler.js
的 createContainer
传入 getElementById(root)
执行 ReactFiberRoot.js
中的createFiberRoot
函数生成的export function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
// 创建RootFiber
const uninitializedFiber = createHostRootFiber(isConcurrent);
let root;
if (enableSchedulerTracing) {
root = ({
// root 节点对应的Fiber对象,fiber对象也是一个树结构,整个应用的顶点
current: uninitializedFiber,
// root 节点, render方法接收的第二个参数
containerInfo: containerInfo,
pendingChildren: null,
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
// 标记渲染过程中是否有错误
didError: false,
// 正在等待提交的任务的expirationTime
pendingCommitExpirationTime: NoWork,
// 记录一次更新渲染过程完成的任务,更新完输出到dom上是读取的这个属性
finishedWork: null,
// 用在 React.Suspense
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
// 标记此次更新要执行的是哪个优先级的任务,更新过程中会遍历到每个节点,每个节点如果有更新就会有自己的expirationTime, Root中会记录整个应用中优先级最高的expirationTime,在更新过程中会根据这个变量去进行更新,如果遍历到某个节点如果这个节点的expirationTime比它大,则说明这个节点的更新优先级排在后面。
nextExpirationTimeToWorkOn: NoWork,
// 用在调度过程中
expirationTime: NoWork,
firstBatch: null,
// 如果存在多个root的情况,进行链表属性标记
nextScheduledRoot: null,
interactionThreadID: unstable_getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
}: FiberRoot);
} else {
...
}
// RootFiber 对象的stateNode 指向 FiberRoot
// FiberRoot.current = RootFiber
uninitializedFiber.stateNode = root;
// The reason for the way the Flow types are structured in this file,
// Is to avoid needing :any casts everywhere interaction tracing fields are used.
// Unfortunately that requires an :any cast for non-interaction tracing capable builds.
// $FlowFixMe Remove this :any cast and replace it with something better.
return ((root: any): FiberRoot);
}
ReactElement
对应一个 Fiber
对象class Component
中的 this.state
、this.props
,在 Fiber 更新后才会更新 class Component
上的 this.state
, props
,也是 hooks
实现的原理,functional Component
是没有 this.state
this.props
的,Fiber
有能力记录这些状态之后在 functional Component
更新后拿到这些状态。ReactElement
对应的树结构
Fiber
数据结构
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
// 标记不同的组件类型,不同的更新方式class component or functional
this.tag = tag;
// key
this.key = key;
// createElement 第一个参数,组件 或者 标签
this.elementType = null;
// 记录组件 resolved 后是 class 还是 functional component
this.type = null;
// 节点的实例,对应 class 组件的实例或者 dom 节点的实例, functional 组件没有实例就没有 stateNode
this.stateNode = null;
// Fiber
// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
this.return = null;
// 单链表结构
// 指向自己的第一个子节点
this.child = null;
// 指向自己的兄弟节点,兄弟节点的 return 指向同一个父节点
this.sibling = null;
this.index = 0;
// ref
this.ref = null;
// 新的变动带来的新的 props
this.pendingProps = pendingProps;
// 老的 props
this.memoizedProps = null;
// 该Fiber节点对应的组件产生的 Update 会存放在这个队列里面
this.updateQueue = null;
// 上一次渲染完成后的老的 State, 新 state 是由 updateQueue 计算出来的然后覆盖这里
this.memoizedState = null;
// context 相关
this.firstContextDependency = null;
// 继承父节点的 mode
this.mode = mode;
// Effects 副作用 用来标记 dom 节点进行哪些更新,用来标记组件执行哪些生命周期
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
// 当前节点产生更新的任务的过期时间
this.expirationTime = NoWork;
// 子节点产生更新的过期时间
this.childExpirationTime = NoWork;
// 在 Fiber 树更新的过程中,每个 Fiber 都会创建一个跟其对应的 Fiber 称之为 workInProgress, 它与 current <==> workInProgress 一一对应,
// current是当前的,workInProgress是要更新的,在更新完成后 workInProgress 是新的状态,current 是老的
// 产生新的 update 要重新渲染,则渲染过程中会复用原有的 alternate,不用在每次更新时创建一个新的对象
this.alternate = null;
if (enableProfilerTimer) {
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
if (__DEV__) {
this._debugID = debugCounter++;
this._debugSource = null;
this._debugOwner = null;
this._debugIsCurrentlyTiming = false;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
}
}
}
用于记录组件状态的改变,记录改变的方式和内容
存放于 updateQueue
中,存放多个 update
用来计算出最终改变的结果
多个 update
可以同时存在, setState
三次会创建三个 update
,放到 updateQueue
里,在进行更新的操作
ReactDOM.render
最终调用 ReactRoot.prototype.render
时会执行到 scheduleRootUpdate
方法里执行 createUpdate
// ReactUpdateQueue.js
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
// 当前更新的过期时间
expirationTime: expirationTime,
// 四种状态
// 更新updateState 0
// 替换replaceState 1
// 强制forceUpdate 2
// throw 捕获 captureUpdate 3
tag: UpdateState,
// 实际执行的操作内容,更新的内容
// 初次渲染传入的是元素 update.payload = { element } ,setState 可能传入的就是对象或者方法
payload: null,
callback: null,
// 下一个 update 单向链表
next: null,
nextEffect: null,
};
}
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
// 每次操作更新后计算出的state,作为下一次更新计算的基础
baseState,
// 记录链表结构
firstUpdate: null,
// 记录链表结构
lastUpdate: null,
// 记录链表结构
firstCapturedUpdate: null,
// 记录链表结构
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
enqueueUpdated
就是在 fiber
对象上创建一个 updateQueue
,然后把 update
对象传入到这个 queue
里
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily.
const alternate = fiber.alternate;
let queue1;
let queue2;
// 通过 ReactDOM.render 初次渲染
if (alternate === null) {
// There's only one fiber.
queue1 = fiber.updateQueue;
queue2 = null;
// 创建一个updateQueue
if (queue1 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else {
// There are two owners.
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
// Neither fiber has an update queue. Create new ones.
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
} else {
// Only one fiber has an update queue. Clone to create a new one.
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
// Only one fiber has an update queue. Clone to create a new one.
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
// 初次渲染 queue2 为null
if (queue2 === null || queue1 === queue2) {
// There's only a single queue.
// 将 update 加入到 updateQueue 里
// fiber.updateQueue.firstUpdate = fiber.updateQueue.lastUpdate = update;
appendUpdateToQueue(queue1, update);
} else {
// There are two queues. We need to append the update to both queues,
// while accounting for the persistent structure of the list — we don't
// want the same update to be added multiple times.
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
// One of the queues is not empty. We must add the update to both queues.
appendUpdateToQueue(queue1, update);
appendUpdateToQueue(queue2, update);
} else {
// Both queues are non-empty. The last update is the same in both lists,
// because of structural sharing. So, only append to one of the lists.
appendUpdateToQueue(queue1, update);
// But we still need to update the `lastUpdate` pointer of queue2.
queue2.lastUpdate = update;
}
}
}
appendUpdateToQueue
方法,初次渲染将 queue
的 firstUpdate
和 lastUpdate
都指向 update
,之后的每有更新创建都将 lastUpdate.next
指向新的 update
,将 lastUpdate
指向新的 update
构建链表结构
function appendUpdateToQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>,
) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
// firstUpdate.next.next.next .... 为单链表结构
// 上次的 next 和 这次的 lastUpdate 都指向新的 update,改变的是 lastUpdate 指针
queue.lastUpdate.next = update;
// 每次更新 lastUpdate
queue.lastUpdate = update;
}
}
在 updateContainer
方法中会计算一个 expirationTime
然后用这个时间创建 update
对象推入 updateQueue
内
export function updateContainer(
element: ReactNodeList, // app
container: OpaqueRoot, // FiberRoot
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
// Fiber
const current = container.current;
// 创建一个时间差
const currentTime = requestCurrentTime();
// 计算出一个时间,ConcurrentMode 会用到, 计算出的是优先级时间
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
requestCurrentTime
方法返回一个固定的常量,调用 recomputeCurrentRendererTime
计算js加载完成到当前渲染时间的时间差值,这个差值范围小(没超过一个单位UNIT_SIZE)的值会在 msToExpirationTime
内被计算成同一个常数,最后赋值全局变量 currentRendererTime
function requestCurrentTime() {
// 已经进入渲染的阶段
if (isRendering) {
return currentSchedulerTime;
}
// ReactDOM.render 执行
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
return currentSchedulerTime;
}
function recomputeCurrentRendererTime() {
const currentTimeMs = now() - originalStartTimeMs;
currentRendererTime = msToExpirationTime(currentTimeMs);
}
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
// 除10 向下取整
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}
computeExpirationForFiber
方法会根据当前渲染的 currentTime 计算出一个优先级时间,核心就是根据渲染方式的 mode 不同来创建不同优先级的 expirationTime, 区别在于传入computeExpirationBucket 的参数不同。
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// 外部强制的情况
// 两种情况赋值 expirationContext ,
// 1、deferredUpdates 计算出 Async 方式的优先级时间
// 2、syncUpdates <- flushSync <- ReactDOM.flushSync , syncUpdates 在执行 fn 之前将
// expirationContext 修改为 Sync,通过外部强制某个更新必须使用那种 expirationTime 的行为
if (expirationContext !== NoWork) {
// An explicit expiration context was set;
expirationTime = expirationContext;
} else if (isWorking) {
// 有任务更新的情况
if (isCommitting) {
// Updates that occur during the commit phase should have sync priority
// by default.
expirationTime = Sync;
} else {
// Updates during the render phase should expire at the same time as
// the work that is being rendered.
expirationTime = nextRenderExpirationTime;
}
} else {
// No explicit expiration context was set, and we're not currently
// performing work. Calculate a new expiration time.
// ConcurrentMode = 0b001; 通过 与 / 或运算便于组合和判断不同的mode
// 异步 mode 才计算 expirationTime
if (fiber.mode & ConcurrentMode) {
// interactiveUpdates 函数里置为true,即计算高优先级的expirationTime
if (isBatchingInteractiveUpdates) {
// This is an interactive update
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// This is an async update
expirationTime = computeAsyncExpiration(currentTime);
}
// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime += 1;
}
// 同步的更新
} else {
// This is a sync update
expirationTime = Sync;
}
}
if (isBatchingInteractiveUpdates) {
// ...
}
return expirationTime;
}
computeExpirationBucket
最终的公式为 ((((currentTime - 2 + 5000(或者150) / 10) / 25(或者10)) | 0) + 1) * 25(或者10),
用 | 0
向下取整,使得在没超过一个单位 25(10)范围内的时间计算出来的值都相同,这样在很短时间内多次 setState 调用更新时,也可以保证是同一优先级的更新。
// 高优先级(动画、交互)
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}
// 低优先级,每 25 往上加的,前后差距在25ms内计算出来的值都一样的。
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
export function computeAsyncExpiration(
currentTime: ExpirationTime,
): ExpirationTime {
return computeExpirationBucket(
currentTime,
LOW_PRIORITY_EXPIRATION,
LOW_PRIORITY_BATCH_SIZE,
);
}
function ceiling(num: number, precision: number): number {
return (((num / precision) | 0) + 1) * precision;
}
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs,
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET +
ceiling(
currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE,
)
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
// workInProgress.alternate 当前fiber
current,
workInProgress,
// 组件类型
Component,
resolvedProps,
// 最高更新优先级
renderExpirationTime,
);
}
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
if (__DEV__) {
// ...
} else {
// Component 即组件的那个方法
nextChildren = Component(nextProps, context);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
// 把 nextChildren 这些 ReactElement 变成 Fiber 对象, 挂载 在 workInProgress.child 上
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
fiber.children
生成 fiber
子树Fiber
对象是否可以复用,在第一次渲染就渲染了 fiber
子树,state
变化可能会导致某些子节点产生变化而不能复用,但是大部分是可以复用的key
优化function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// 首次渲染
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
// 更新渲染
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child, // 再次渲染时才会有子节点
nextChildren,
renderExpirationTime,
);
}
}
// 更新渲染
export const reconcileChildFibers = ChildReconciler(true);
// 首次渲染
export const mountChildFibers = ChildReconciler(false);
reconcileChildFibers
是 ChildReconciler
最终返回的函数newChild
是不是 Fragment
节点,如果是 Fragment
则将 newChild
赋值为 newChild.props.children
newChild
是不是 isObject
REACT_ELEMENT_TYPE
:reconcileSingleElement
REACT_PORTAL_TYPE
: reconcileSinglePortal
string or number
:reconcileSingleTextNode
isArray
:reconcileChildrenArray
iterato
r 函数: reconcileChildrenIterator
isObject
,则抛出错误newChild
为 undefined
并且非 Fragment
,提醒没有返回值null
的情况,新的 props
的 children
为 null
,则把现有的所有 children
都删掉function reconcileChildFibers(
// workInProgress
returnFiber: Fiber,
// 原有子节点 current.child
currentFirstChild: Fiber | null,
// 组件执行后返回的 nextChildren, nextChildren = Component(nextProps, context);
newChild: any,
// 最高优先级过期时间
expirationTime: ExpirationTime,
): Fiber | null {
// 判断是不是 Fragment
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果是 Fragment,只渲染 props.children 就行了
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
// 对象类型
if (isObject) {
switch (newChild.$$typeof) {
// React.element
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
// React.portal
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// 文本类型
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}
// 数组类型
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// 迭代器
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// 还是对象那么就抛出错误
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// 没有返回值的情况,给提示
}
// 为 null 的情况,删除所有子节点
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
elementType
来创建新的节点function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
// 新 child 的key
const key = element.key;
// 已有的fiber节点,更新渲染才不为 null
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
// 判断新老的key是否相同
if (child.key === key) {
if (
// 原 child 是 Fragment 且新 element type 也是 Fragment
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
// 或者判断 elementType 是否相同
: child.elementType === element.type
) {
// 复用当前节点,删除不用的兄弟节点(这里删除都是标记删除,而非真正删除)
deleteRemainingChildren(returnFiber, child.sibling);
// 复用这个老的child
const existing = useFiber(
child,
element.type === REACT_FRAGMENT_TYPE
? element.props.children
: element.props,
expirationTime,
);
existing.ref = coerceRef(returnFiber, child, element);
// 指定父节点
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
// 返回复用的节点
return existing;
} else {
// key 相同,但类型不相同,删除老的child,退出循环
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// 不相同则全部删除
deleteChild(returnFiber, child);
}
// 兄弟节点继续寻找可复用的
child = child.sibling;
}
// 创建新的节点
if (element.type === REACT_FRAGMENT_TYPE) {
// Fragment 传的elements传入的是 element.props.children, --> createFiber接收的 penddingProps 即这个 children
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 根据 element type来创建不同 fiber 对象
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
function useFiber(
fiber: Fiber,
pendingProps: mixed,
expirationTime: ExpirationTime,
): Fiber {
// 复用的时候 fiber.alternate 已不为 null,就不需要再 creatFiber 了
const clone = createWorkInProgress(fiber, pendingProps, expirationTime);
clone.index = 0;
clone.sibling = null;
return clone;
}
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
// 清空副作用
childToDelete.nextEffect = null;
// 标记删除,更新 fiberTree,直接删除会影响到 dom 节点,真正删除在 commit 阶段,这里的任务是可以中断的,而commit 是不可中断的
childToDelete.effectTag = Deletion;
}
function deleteRemainingChildren(
returnFiber: Fiber, // 当前更新的节点
currentFirstChild: Fiber | null, // 子节点
): null {
// 首次渲染 直接返回
if (!shouldTrackSideEffects) {
// Noop.
return null;
}
let childToDelete = currentFirstChild;
// 循环删除要删除的子节点的兄弟节点
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
尽量减少数组的遍历次数来达到判断节点是否可复用的过程
第一次遍历
newIdx
则为能够有几个能够复用的节点的index
updateSlot
中通过判断新老 Key
是否相同来复用
updateSlot
返回 null
表示不能复用, 直接 break
oldFiber
有 key
,而 newChild
是 textNode
直接返回 null
, 因为文本节点是没有 Key
的textNode、Fragment、Element
都会判断 oldFiber
不为 null
就复用,为 null
则创建新的第一次遍历完 或者 break
后
newIdx
等于 newChildren.length
说明已经在 updateSlot
中创建新的对象了, 新数组操作完成了, 所有新节点都已经创建
oldFiber
oldFiber
为 null
,老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的最后 newChildren
还没创建完,oldFiber
还不为 null
的情况,数组存在顺序的变化
updateFromMap
: 根据 key
或 index
创建 map
对象,通过 map
对象在 oldFiber
里找到可以复用的或者创建新的function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// 要返回的结果
let resultingFirstChild: Fiber | null = null;
// 用于构建链结构的中间变量
let previousNewFiber: Fiber | null = null;
// 上一次渲染完成后第一个 child 节点, current.child
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
// 下一个老的节点
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 老的 fiber 的 index 位置与新的不同,位置不匹配
if (oldFiber.index > newIdx) {
// 将 oldFiber 赋值给 nextOldFiber 暂存
nextOldFiber = oldFiber;
oldFiber = null;
} else {
// 没变化则下一个老的节点为当前的节点的兄弟节点
nextOldFiber = oldFiber.sibling;
}
//
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
// key 相同根据类型确定复用类型
// 文本节点没有key,为 null 表示不能复用, 能复用也有复用复用节点和新建节点的区分
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
// 重置 oldFiber,跳出循环,即找到不能复用的那个节点为止的fiber,那么 newIdx 则表示有多少个节点相同的那个index
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
// 更新渲染的情况
if (shouldTrackSideEffects) {
// 没有复用,说明这是这是创建新的节点, 则要标记删除老的
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// previousNewFiber 中间变量,将数组构建成链表结构
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 首个子节点
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
// 构建链表结构
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 新数组操作完,所有新的 children 都全部创建 fiber 节点了
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
// 删除老的
deleteRemainingChildren(returnFiber, oldFiber);
// 返回首个子节点,其他节点是通过 sibling 指向下去
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
// 老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (!newFiber) {
continue;
}
// 同样 placeChild 标记移动位置
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// 构建链表结构
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
// newChildren 还没创建完,oldFiber 还不为null
// 数组存在顺序的变化,在 oldFiber 里找到可以复用的,通过 map 快速寻找
// 根据 key 或 index 创建 map 对象
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 一次完整的遍历
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
// 根据 map 找可以复用的节点 或 创建新节点,跟 updateSlot 相似
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber) {
if (shouldTrackSideEffects) {
// 复用的情况
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
// 从 map 里删除
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
// 最后剩下的是没有被复用的,全部删除
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
Component
ReactDOM.render App
组件时会创建 FiberRoot -> createHostRootFiber
,标记这个 fiber
的 tag
为 HostRoot
function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(isConcurrent);
// ....
}
function createHostRootFiber(isConcurrent: boolean): Fiber {
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
第一次执行到 beginWork
时首次更新的是 HostRoot
,在 updateHostRoot
中会调用 reconcileChildFibers
调和子节点,reconcileSingleElement -> createFiberFromElement -> createFiberFromTypeAndProps
, 默认 fiberTag
为 IndeterminateComponent
,只有当 class 组件有
construct时才标记为
class` 组件,
function
类型组件开始默认都是 IndeterminateComponent
function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiber;
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
// component 组件
fiberTag = ClassComponent;
}
// 原生 dom 标签
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
case REACT_CONCURRENT_MODE_TYPE:
return createFiberFromMode(
pendingProps,
mode | ConcurrentMode | StrictMode,
expirationTime,
key,
);
case REACT_STRICT_MODE_TYPE:
return createFiberFromMode(
pendingProps,
mode | StrictMode,
expirationTime,
key,
);
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
// ...
}
}
}
fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
case IndeterminateComponent: {
const elementType = workInProgress.elementType;
return mountIndeterminateComponent(
current,
workInProgress,
elementType,
renderExpirationTime,
);
}
function mountIndeterminateComponent(
_current,
workInProgress,
Component,
renderExpirationTime,
) {
// 只有在第一次渲染的时候,才会有 mountIndeterminateComponent 这种情况,非首次渲染,已经发现 mountIndeterminateComponent 的具体的类型
// 如果在第一次渲染中,current 存在,可能是中途抛出 error 或者 promise的情况,就是 suspended 组件的情况,把 current 和 workInprogress 的联系去除,需要重新创建 workInprogress,重新进行一次初次渲染的流程
if (_current !== null) {
// An indeterminate component only mounts if it suspended inside a non-
// concurrent tree, in an inconsistent state. We want to tree it like
// a new mount, even though an empty version of it already committed.
// Disconnect the alternate pointers.
_current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// context 相关
const props = workInProgress.pendingProps;
const unmaskedContext = getUnmaskedContext(workInProgress, Component, false);
const context = getMaskedContext(workInProgress, unmaskedContext);
prepareToReadContext(workInProgress, renderExpirationTime);
let value;
if (__DEV__) {
// ...
} else {
// 调用方法
value = Component(props, context);
}
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if (
typeof value === 'object' &&
value !== null &&
// 符合 class 组件条件
// 这里如果函数里返回一个具有 render Function 属性的对象,也会被当作 class 组件调用
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext = false;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
Component,
getDerivedStateFromProps,
props,
);
}
adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderExpirationTime);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderExpirationTime,
);
} else {
// 否则标记为 FunctionComponent,直接调和子节点
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
if (__DEV__) {
// ...
}
// value 即子节点
reconcileChildren(null, workInProgress, value, renderExpirationTime);
return workInProgress.child;
}
}
dom
原生标签对应的 fiber
节点state
概念type
和 props
判断标签内容是不是纯文本,如果是纯文本则没有新子节点;如果不是并且 prevProps
是纯文本那么标记 effectTag
function updateHostComponent(current, workInProgress, renderExpirationTime) {
pushHostContext(workInProgress);
if (current === null) {
// 服务端渲染,复用节点
tryToClaimNextHydratableInstance(workInProgress);
}
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
// 判断是否为纯文本节点的 children
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// 纯文本节点,则没有children
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
// 原来的 props 值是纯文本,新的 props 是节点,标记替换
workInProgress.effectTag |= ContentReset;
}
// 标记 ref,仅有在 classComponent 和 HostComponent (原生标签)中标记ref
markRef(current, workInProgress);
// Check the host config to see if the children are offscreen/hidden.
if (
renderExpirationTime !== Never &&
workInProgress.mode & ConcurrentMode && // ConcurrentMode
shouldDeprioritizeSubtree(type, nextProps) // !!props.hidden;
) {
// 异步渲染模式下,具有 hidden 属性的组件永远不被更新
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = Never;
return null;
}
// 调和创建子节点
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
// shouldSetTextContent
// 只能渲染文本的标签 textarea、option、noscript
// children 为 string、number直接渲染为文本
// 使用了 dangerouslySetInnerHTML 属性的
function shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
type === 'option' ||
type === 'noscript' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
props.dangerouslySetInnerHTML.__html != null)
);
}
complete
阶段才插入到 dom
中function updateHostText(current, workInProgress) {
if (current === null) {
// 服务端渲染
tryToClaimNextHydratableInstance(workInProgress);
}
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
return null;
}
fiber
的时候(调和子节点里面)处理 ref
commit
开始前,第二次循环执行 commitAllHostEffects
(dom
的增、删、改)过程中,调用 commitDetachRef
将 ref
从之前挂载的地方卸载commitAllLifeCycles
调用 commitAttachRef
将更新过后的节点挂载到 ref
上,如果 ref 是函数
则执行 ref 函数
将 fiber
的 instance
作为参数传入// createRef
function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject);
}
return refObject;
}
ref
,需要创建一个 ref 方法
,挂载在 fiber
的 ref
属性上,在这个方法里将更新过的 dom
节点或者 class instanc
e 挂载在实例的 refs[stringRef]
上(即 commitAttachRef
过程)this.refs.stringRef
获取 ref
,即将被废弃// ReactChildFiber.js 调和子节点复用或者创建新的fiber 节点时处理 ref
function coerceRef(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
) {
let mixedRef = element.ref;
if (
mixedRef !== null &&
typeof mixedRef !== 'function' &&
typeof mixedRef !== 'object'
) {
// 处理字符串 ref
// createElement -> 传入 owner 为 ReactCurrentOwner.current ,而在 finishClassComponent 中设置 ReactCurrentOwner.current= workInProgress;
// 之后子节点会在 instance.render 当中被创建,进入到调和子节点中
// _owner 即 fiber 对象
if (element._owner) {
const owner: ?Fiber = (element._owner: any);
let inst;
if (owner) {
const ownerFiber = ((owner: any): Fiber);
// classComponent 的实例
inst = ownerFiber.stateNode;
}
const stringRef = '' + mixedRef;
// Check if previous string ref matches new string ref
if (
current !== null &&
current.ref !== null &&
typeof current.ref === 'function' &&
current.ref._stringRef === stringRef // _stringRef 如果没有变化,不需要重新生成 ref 方法
) {
return current.ref;
}
const ref = function(value) {
// 即组件里使用的 this.refs
let refs = inst.refs;
if (refs === emptyRefsObject) {
// This is a lazy pooled frozen object, so we need to initialize.
refs = inst.refs = {};
}
// dom 节点 或者 instance 被挂载的时候会调用 ref 这个方法,value 即传入它自己的实例
if (value === null) {
delete refs[stringRef];
} else {
// 设置到 this.refs 的 stringRef 属性上
refs[stringRef] = value;
}
};
ref._stringRef = stringRef;
// 这个方法在 commit 阶段被调用
return ref;
} else {
// 。。。
}
}
return mixedRef;
}
commitAllHostEffects
先卸载原有的 ref
// dom 节点的插入、删除、更新
function commitAllHostEffects() {
while (nextEffect !== null) {
recordEffect();
const effectTag = nextEffect.effectTag;
// 判断内部是否为文字节点
if (effectTag & ContentReset) {
// 文字节点设置为空字符串
commitResetTextContent(nextEffect);
}
// ref操作
if (effectTag & Ref) {
const current = nextEffect.alternate;
// 非首次渲染
if (current !== null) {
commitDetachRef(current);
}
}
// ...
}
}
// commitDetachRef
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
if (typeof currentRef === 'function') {
// 如果是函数则传入 null 执行
currentRef(null);
} else {
// 其他类型直接赋值 null
currentRef.current = null;
}
}
}
commitAllLifeCycles
中将更新后的 instance
挂载到 ref
上function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
let instanceToUse;
// 获取 instance
switch (finishedWork.tag) {
case HostComponent:
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
if (typeof ref === 'function') {
// 这里即函数式 ref 真正挂载 instance, 字符串 ref 创建的 ref 函数也在这执行,将 this.refs[stringRef] = instanceToUse
ref(instanceToUse);
} else {
// 其他类型直接赋值 ref.current
ref.current = instanceToUse;
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.