1.forwardRef
用来解决高阶组件传递ref
的问题。它的用法就像是使用redux
的时候,在外面包裹一层,通过...props
的方式把外部的props
传入到实际组件
const TargetComponent = React.forwardRef((props, ref) => (
<TargetComponent ref={ref} />
))
2.ReactElement
ReactElement
通过createElement
创建,调用该方法需要传入三个参数:
- type
- config
- children
type
指代这个ReactElement
的类型
- 字符串比如
div
,p
代表原生DOM,称为HostComponent
- Class类型是我们继承自
Component
或者PureComponent
的组件,称为ClassComponent
- 方法就是
functional Component
- 原生提供的
Fragment
、AsyncMode
等是Symbol,会被特殊处理 - TODO: 是否有其他的
从源码可以看出虽然创建的时候都是通过config
传入的,但是key
和ref
不会跟其他config
中的变量一起被处理,而是单独作为变量出现在ReactElement
上。
从源码可以看出虽然创建的时候都是通过config
传入的,但是key
和ref
不会跟其他config
中的变量一起被处理,而是单独作为变量出现在ReactElement
上。在最后创建ReactElement
我们看到了这么一个变量$$typeof
,在这里他是一个常量:REACT_ELEMENT_TYPE
3.React Children
React
上面有有一个Children
对象,上面有着Children
定义
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
......
}
找到定义
function mapChildren(children, func, context) {
if (children == null) {
return children
}
const result = []
mapIntoWithKeyPrefixInternal(children, result, null, func, context)
return result
}
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
let escapedPrefix = ''
if (prefix != null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/'
}
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
)
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext)
releaseTraverseContext(traverseContext)
}
可以看到后面调用了getPooledTraverseContext
,这是从内容池中找到当前的contex
t对象,操作完了之后releaseTraverseContext
会把当前的context
对象清空然后放回到pool
中。然后调用traverseAllChildren
,这个方法的作用就是递归调用mapChildren
将所有Children
铺平,直到当前元素是一个有效的ReactElement
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const { result, keyPrefix, func, context } = bookKeeping
let mappedChild = func.call(context, child, bookKeeping.count++)
if (Array.isArray(mappedChild)) {
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c)
} else if (mappedChild != null) {
if (isValidElement(mappedChild)) {
mappedChild = cloneAndReplaceKey(
mappedChild,
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: '') +
childKey,
)
}
result.push(mappedChild)
}
}
使用方法
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
4.React中的数据结构
Update
&UpdateQueue
export type Update<State> = {
// 更新的过期时间
expirationTime: ExpirationTime,
// export const UpdateState = 0;
// export const ReplaceState = 1;
// export const ForceUpdate = 2;
// export const CaptureUpdate = 3;
// 指定更新的类型,值为以上几种
tag: 0 | 1 | 2 | 3,
// 更新内容,比如`setState`接收的第一个参数
payload: any, // 当前DOM元素?
// 对应的回调,`setState`,`render`都有
callback: (() => mixed) | null,
// 指向下一个更新
next: Update<State> | null,
// 指向下一个`side effect`
nextEffect: Update<State> | null,
};
export type UpdateQueue<State> = {
// 每次操作完更新之后的`state`
baseState: State,
// 队列中的第一个`Update`
firstUpdate: Update<State> | null,
// 队列中的最后一个`Update`
lastUpdate: Update<State> | null,
// 第一个捕获类型的`Update`
firstCapturedUpdate: Update<State> | null,
// 最后一个捕获类型的`Update`
lastCapturedUpdate: Update<State> | null,
// 第一个`side effect`
firstEffect: Update<State> | null,
// 最后一个`side effect`
lastEffect: Update<State> | null,
// 第一个和最后一个捕获产生的`side effect`
firstCapturedEffect: Update<State> | null,
lastCapturedEffect: Update<State> | null,
};
5.初次ReactDOM.reander过程
首先调用legacyRenderSubtreeIntoContainer
ReactDOM = {
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
)
},
}
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
let root: Root = (container._reactRootContainer: any)
if (!root) {
// 生成root,当前传入的根container的_reactRootContainer指向生成的root
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
)
if (typeof callback === 'function') {
const originalCallback = callback
callback = function() {
const instance = DOMRenderer.getPublicRootInstance(root._internalRoot)
originalCallback.call(instance)
}
}
// 首次render不比unbatchedUpdates
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) {
// 一般不会出现
} else {
root.render(children, callback)
}
})
} else {
// 有root的情况
}
return DOMRenderer.getPublicRootInstance(root._internalRoot)
}
legacyCreateRootFromDOMContainer
方法生成root
,返回的结果上的_internalRoot
指向这个root
。同时会调用DOMRenderer.createContainer
创建FiberRoot
,然后生成的root
的current
属性指向这个FiberRoot
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container)
// First clear any existing content.
if (!shouldHydrate) {
let warned = false
let rootSibling
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling)
}
}
// Legacy roots are not async by default.
const isConcurrent = false
return new ReactRoot(container, isConcurrent, shouldHydrate)
}
function ReactRoot(
container: Container,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = DOMRenderer.createContainer(container, isConcurrent, hydrate)
this._internalRoot = root
}
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
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
}
可以看到还有个updateContainer
方法
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current
const currentTime = requestCurrentTime()
const expirationTime = computeExpirationForFiber(currentTime, current)
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
)
}
export function updateContainerAtExpirationTime(
element: ReactNodeList,
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
const context = getContextForSubtree(parentComponent)
if (container.context === null) {
container.context = context
} else {
container.pendingContext = context
}
return scheduleRootUpdate(current, element, expirationTime, callback)
}
可以看到先生成一个时间const expirationTime
,也就是这次更新的超时时间,然后调用updateContainerAtExpirationTime
,然后调用scheduleRootUpdate
进行调度
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
const update = createUpdate(expirationTime)
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
}
enqueueUpdate(current, update)
scheduleWork(current, expirationTime)
return expirationTime
}
scheduleRootUpdate
会调用createUpdate
创建Update
对象,其playload
为container
上面的ReactNodeList
,然后调用enqueueUpdate
生成一个双向列表队列,然后调用scheduleWork
开始调用。上面的过程都是依赖的FiberRoot
6.expirationTime
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
export type ExpirationTime = number;
export const NoWork = 0;
export const Never = 1;
export const Sync = MAX_SIGNED_31_BIT_INT;
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
// Always add an offset so that we don't clash with the magic number for NoWork.
return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}
export function expirationTimeToMs(expirationTime: ExpirationTime): number {
return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_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(
MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE,
)
);
}
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,
);
}
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,
);
}
最后的计算公式为((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25
(公式存疑),也就是说最后的结果是以25ms
为单位向上增加的,在25ms
内,react
这样做为的是让两次时间非常想尽的更新得到同样的expirationTime
,然后他们的优先级就会判定一样,就会再一次更新中完成,也就是一个batchedUpdates
计算当前currentTime
function requestCurrentTime() {
if (isRendering) {
return currentSchedulerTime
}
findHighestPriorityRoot()
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
recomputeCurrentRendererTime()
currentSchedulerTime = currentRendererTime
return currentSchedulerTime
}
return currentSchedulerTime
}
react
为了避免每次计算expirationTime
都要Date now
,所以用currentRendererTime
来保存了这个值,上面的currentSchedulerTime
也是记录这个值的
首先
if (isRendering) {
return currentSchedulerTime
}
这个isRendering
只有在performWorkOnRoot
的时候才会被设置为true
,而其本身是一个同步的方法,不存在他执行到一半没有设置isRendering
为false
的时候就跳出,那么什么情况下会在这里出现新的requestCurrentTime
呢?
- 在生命周期方法中调用了
setState
- 需要挂起任务的时候
也就是说 React 要求在一次rendering
过程中,新产生的update
用于计算过期时间的current
必须跟目前的renderTime
保持一致,同理在这个周期中所有产生的新的更新的过期时间都会保持一致!
findHighestPriorityRoot()
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
recomputeCurrentRendererTime()
currentSchedulerTime = currentRendererTime
return currentSchedulerTime
}
findHighestPriorityRoot
找到优先级最高的节点,同时设置nextFlushedExpirationTime
,然后if判断当前队列中没有更新的任务,就会重新计算当前时间,否则,还是返回最初的currentSchedulerTime
7.各种expirationTime
root.expirationTime
root.nextExpirationTimeToWorkOn
root.childExpirationTime
root.earliestPendingTime & root.lastestPendingTime
root.earliestSuspendedTime & root.lastestSuspendedTime
root.lastestPingedTime
nextFlushedExpirationTime
nextLatestAbsoluteTimeoutMs
currentRendererTime
currentSchedulerTime
React
更新是从FilberRoot
开始的,所以每次更新时都会遍历向上查找FiberRoot
,而这个childExpirationTime
会设置到每个父节点,代表其子节点expirationTime
pendingTime
,在FiberRoot
上有两个值earliestPendingTime
和lastestPedingTime
,他们是一对值,用来记录所有子树中需要进行渲染的更新的expirationTime
的区间
suspendedTime
,同样的在ReactFiber
上有两个值earliestSuspendedTime
和lastestSuspendedTime
,这两个值是用来记录被挂起的任务的过期时间的
首先我们定义一下什么情况下任务是被挂起的:
- 出现可捕获的错误并且还有优先级更低的任务的情况下
- 当捕获到
thenable
,并且需要设置onTimeout
的时候
root.expirationTime
是用来标志当前渲染的过期时间的,请注意他只管本渲染周期,他并不管你现在的渲染目标是哪个,渲染目标是由root.nextExpirationTimeToWorkOn
来决定的。
那么他们有什么区别呢?主要区别在于发挥作用的阶段
expirationTime
作用于调度阶段,主要指责是:
- 决定是异步执行渲染还是同步执行渲染
- 作为
react-scheduler
的timeout
标准,决定是否要优先渲染
nextExpirationTimeToWorkOn
主要作用于渲染阶段:
- 决定那些更新要在当前周期中被执行
- 通过跟每个节点的
expirationTime
比较决定该节点是否可以直接bailout
(跳过)
他们都是通过pendingTime
、suspenededTime
和pingedTime
中删选出来的,唯一的不同是,nextExpirationTimeToWorkOn
在没有pending
或者pinged
的任务的时候会选择最晚的suspendedTime
,而expirationTime
会选择最早的
expirationTime
的变化:
- 在
scheduleWork
的时候通过markPendingExpirationTime
设置 - 在
beginWork
的时候被设置为NoWork
- 在
onUncaughtError
的时候设置为NoWork
onSuspend
的时候又会设置回当次更新的expirationTime
8.scheduleWork
上面说的在创建完更新队列后,就会调用scheduleWork
调度了
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
const root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
if (__DEV__) {
switch (fiber.tag) {
case ClassComponent:
warnAboutUpdateOnUnmounted(fiber, true);
break;
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
warnAboutUpdateOnUnmounted(fiber, false);
break;
}
}
return;
}
// 此条件的意思是当前处理空闲阶段,没有render或者commit,并且下一次任务不为空,还有当前过期时间(优先级)高于下次的过期时间
// 则清空队列
// 例如上一个任务是异步任务(优先级很低,超时时间是 502ms),并且在上一个时间片(初始是 33ms)任务没有执行完,而且等待下一次requestIdleCallback的时候新的任务进来了,并且超时时间很短(52ms 或者 22ms 甚至是 Sync),那么优先级就变成了先执行当前任务,也就意味着上一个任务被打断了(interrupted)
if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime > nextRenderExpirationTime
) {
interruptedBy = fiber;
resetStack();
}
// 记录所有子节点的任务时间区间
markPendingPriorityLevel(root, expirationTime);
// 要么处于没有 work 的状态,要么只能在 render 阶段,要么有两个不同的root
if (
!isWorking ||
isCommitting ||
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
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.',
);
}
}
首先是调用scheduleWorkToRoot
,这个方法的主要是更新当前任务对应Filber
的到期时间
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
recordScheduleUpdate();
if (__DEV__) {
if (fiber.tag === ClassComponent) {
const instance = fiber.stateNode;
warnAboutInvalidUpdates(instance);
}
}
// 如果filber上的过期时间小于当前的过期时间,更新
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
let alternate = fiber.alternate;
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// fiber.return 返回当前fiber的父节点
let node = fiber.return;
let root = null;
if (node === null && fiber.tag === HostRoot) {
// 如果filber.return的值为null,设置root的值为根节点(fiber.stateNode)
root = fiber.stateNode;
} else {
// 循环向上遍历,更新每个子节点的过期时间
// 如果子节点的过期时间小于当前过期时间,则更新
while (node !== null) {
alternate = node.alternate;
if (node.childExpirationTime < expirationTime) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
......
return root;
}
requestWork
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将root加入调度队列中,不会存在两个相同的 root 前后出现在队列中
addRootToSchedule(root, expirationTime)
if (isRendering) {
return
}
if (isBatchingUpdates) {
if (isUnbatchingUpdates) {
nextFlushedRoot = root
nextFlushedExpirationTime = Sync
performWorkOnRoot(root, Sync, true)
}
return
}
// 根据expirationTime调用performSyncWork还是scheduleCallbackWithExpirationTime
if (expirationTime === Sync) {
performSyncWork()
} else {
// 异步调度,并且当前已经有异步任务,如果当前传入的任务优先级高,取消之前的调度,异步调度当前任务
// 无异步任务,生成一个到期时间,调度
scheduleCallbackWithExpirationTime(root, expirationTime)
}
}
9.performWork
performAsyncWork
为异步调用,一开始的shouldYieldToRenderer
代表是否当前调度中有没有时间执行异步任务,若shouldYieldToRenderer
返回false
,则代表有,那么重新计算当前的expirationTime
,更新所有的已过期的root
任务的nextExpirationTimeToWorkOn
let didYield: boolean = false;
function shouldYieldToRenderer() {
if (didYield) {
return true;
}
if (shouldYield()) {
didYield = true;
return true;
}
return false;
}
function performAsyncWork() {
try {
if (!shouldYieldToRenderer()) {
// 如果当前还有调度的链表
if (firstScheduledRoot !== null) {
// 重新计算当前的一个expirationTime
recomputeCurrentRendererTime();
let root: FiberRoot = firstScheduledRoot;
do {
// 设置所有的已过期的root任务的nextExpirationTimeToWorkOn为当前计算出的expirationTime
didExpireAtExpirationTime(root, currentRendererTime);
root = (root.nextScheduledRoot: any);
} while (root !== firstScheduledRoot);
}
}
performWork(NoWork, true);
} finally {
didYield = false;
}
}
performSyncWork
同步调度任务,方法很简单,直接调用performWork
function performSyncWork() {
performWork(Sync, false);
}
performWork
此方法为真正的调度方法,首先会调用findHighestPriorityRoot
,找到当前队列优先级最高的root
任务,命名为nextFlushedRoot
,已经其对应的expirationTime
,命名为nextFlushedExpirationTime
function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) {
findHighestPriorityRoot();
if (isYieldy) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
if (enableUserTimingAPI) {
const didExpire = nextFlushedExpirationTime > currentRendererTime;
const timeout = expirationTimeToMs(nextFlushedExpirationTime);
stopRequestCallbackTimer(didExpire, timeout);
}
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
minExpirationTime <= nextFlushedExpirationTime &&
!(didYield && currentRendererTime > nextFlushedExpirationTime)
) {
performWorkOnRoot(
nextFlushedRoot,
nextFlushedExpirationTime,
currentRendererTime > nextFlushedExpirationTime,
);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
} else {
while (
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
minExpirationTime <= nextFlushedExpirationTime
) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
findHighestPriorityRoot();
}
}
if (isYieldy) {
callbackExpirationTime = NoWork;
callbackID = null;
}
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(
((nextFlushedRoot: any): FiberRoot),
nextFlushedExpirationTime,
);
}
finishRendering();
}
可以看到,不管同步异步,都有
while(
nextFlushedRoot !== null &&
nextFlushedExpirationTime !== NoWork &&
minExpirationTime <= nextFlushedExpirationTime
)
这是一个循环调度root
任务的过程,判断条件为调度队列不为空,并且当前传入的任务优先级小于优先级最高的任务。异步调用时还有个额外的条件!(didYield && currentRendererTime > nextFlushedExpirationTime)
,翻译过来就是当前传入的异步任务没过期,并且当前的渲染时间也没超过优先级最高的任务。然后这个循环就会在每次循环中调用performWorkOnRoot
10.performWorkOnRoot
这里是整个调度过程的提交、render过程。不同的是,在异步任务中有个判断
if (!shouldYieldToRenderer()) {
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = finishedWork;
}
意思就是说如果当前没有时间处理异步任务,直接completeRoot
function performWorkOnRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isYieldy: boolean,
) {
invariant(
!isRendering,
'performWorkOnRoot was called recursively. This error is likely caused ' +
'by a bug in React. Please file an issue.',
);
isRendering = true;
if (!isYieldy) {
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
if (!shouldYieldToRenderer()) {
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = finishedWork;
}
}
}
}
isRendering = false;
}
11.renderRoot
这个判断的意思就是如果当前没有其他的任务,那么就进行一些初始化的工作
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null
) {
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
......
}
然后调用workLoop
,对于异步任务,仍要判断是否有剩余时间处理此任务
do {
try {
workLoop(isYieldy);
} catch (thrownValue) {
}
break;
} while (true);
function workLoop(isYieldy) {
if (!isYieldy) {
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
如果在这过程中会有报错,则停止调度,会在上面的catch中
处理,并且设置finishedWork
为null
,从而结束这一次的render
performUnitOfWork
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
startWorkTimer(workInProgress);
let next;
......
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
if (next === null) {
next = completeUnitOfWork(workInProgress);
}
......
ReactCurrentOwner.current = null;
return next;
}
首先执行next = beginWork(current, workInProgress, nextRenderExpirationTime)
对节点进行操作,其内部会针对不同类型的节点进行创建,返回的结果即会赋值给next
,return next
。 如果next === null
,说明子节点已经处理完,调用next = completeUnitOfWork(workInProgress)
。
completeUnitOfWork
会从下往上遍历,处理effact tag
,如果到root
节点返回的是null
,代表整棵树的遍历已经结束了,如果找到了其他兄弟节点,就返回这个节点,重新走一遍workLoop
12.updateClassComponent
在beginWork
中,会针对不同的节点类型来调用不同的方法处理,这里以ClassComponent
为例
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
......
// 对context进行处理
let hasContext;
// 如果当前组件是context provider
// 设置hasContext标记
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
const instance = workInProgress.stateNode;
let shouldUpdate;
// 如果实例为空
if (instance === null) {
if (current !== null) {
current.alternate = null;
workInProgress.alternate = null;
workInProgress.effectTag |= Placement;
}
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 挂载这个实例
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else if (current === null) {
// 第一次渲染时,current为null
// 重新挂载实例
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
} else {
// 更新类组件的时候
// 调用updateClassInstance
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
......
// 调用finishClassComponent
// 如果需要更新,调用reconcileChildren,返回更新后的workInProgress
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
return nextUnitOfWork;
}
constructClassInstance
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
renderExpirationTime: ExpirationTime,
): any {
.......
adoptClassInstance(workInProgress, instance);
}
adoptClassInstance
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
setInstance(instance, workInProgress);
......
}
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
},
enqueueReplaceState(inst, payload, callback) {
},
enqueueForceUpdate(inst, callback) {
};
classComponentUpdater
里面几个方法,即对应着setState
、replaceState
、forceUpdate
mountClassInstance
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
if (__DEV__) {
checkClassInstance(workInProgress, ctor, newProps);
}
const instance = workInProgress.stateNode;
// 初始化 props、state 等实例属性
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);
}
.......
// 如果有updateQueue就更新
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
// 如果定义了getDerivedStateFromProps周期函数,则调用
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
instance.state = workInProgress.memoizedState;
}
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
callComponentWillMount(workInProgress, instance);
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
}
// 判断是否有componentDidMount,
// componentDidMount要在真正渲染进DOM之后才调用,也就是commit之后
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
}
13.commitRoot
调用完renderRoot
之后,fiber
中对应的每个节点都执行了render
之前所有的生命周期函数,并创建了对应的DOM元素,放在workInProgress.stateNode
中。然后调用commitRoot
,提交子树中优先级最高的任务,更新DOM元素的属性后,挂载组件,调用componentDidMount