React DOM component tree
一、作用
二、分离已删除实例
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function detachDeletedInstance(node: Instance): void {
if (enableInternalInstanceMap) {
internalInstanceMap.delete(node);
internalPropsMap.delete(node);
delete (node as any)[internalEventHandlersKey];
delete (node as any)[internalEventHandlerListenersKey];
delete (node as any)[internalEventHandlesSetKey];
delete (node as any)[internalRootNodeResourcesKey];
if (__DEV__) {
delete (node as any)[internalInstanceKey];
}
return;
}
// TODO: This function is only called on host components. I don't think all of
// these fields are relevant.
// 待办:这个函数只在宿主组件上调用。我认为并非所有这些字段都是相关的。
delete (node as any)[internalInstanceKey];
delete (node as any)[internalPropsKey];
delete (node as any)[internalEventHandlersKey];
delete (node as any)[internalEventHandlerListenersKey];
delete (node as any)[internalEventHandlesSetKey];
}
三、预缓存Fiber节点
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function precacheFiberNode(
hostInst: Fiber,
node:
| Instance
| TextInstance
| SuspenseInstance
| ActivityInstance
| ReactScopeInstance,
): void {
if (enableInternalInstanceMap) {
internalInstanceMap.set(node, hostInst);
if (__DEV__) {
(node as any)[internalInstanceKey] = hostInst;
}
return;
}
(node as any)[internalInstanceKey] = hostInst;
}
四、将容器标记为根
export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
node[internalContainerInstanceKey] = hostRoot;
}
五、取消将容器标记为根
export function unmarkContainerAsRoot(node: Container): void {
node[internalContainerInstanceKey] = null;
}
六、容器是否标记为根
export function isContainerMarkedAsRoot(node: Container): boolean {
return !!node[internalContainerInstanceKey];
}
七、从节点获取最近的实例
备注
enableInternalInstanceMap()由 ReactFeatureFlags#enableInternalInstanceMap 提供getParentHydrationBoundary()由 ReactFiberConfigDOM#getParentHydrationBoundary 实现
// Given a DOM node, return the closest HostComponent or HostText fiber ancestor.
// 给定一个 DOM 节点,返回最接近的 HostComponent 或 HostText fiber 祖先节点。
// If the target node is part of a hydrated or not yet rendered subtree, then
// this may also return a SuspenseComponent, ActivityComponent or HostRoot to
// indicate that.
// 如果目标节点属于已水化或尚未渲染的子树,则可能也会返回 SuspenseComponent、
// ActivityComponent 或 HostRoot
// Conceptually the HostRoot fiber is a child of the Container node. So if you
// pass the Container node as the targetNode, you will not actually get the
// 从概念上讲,HostRoot fiber 是 Container 节点的子节点。因此,如果将 Container 节点作为 targetNode 传入,你实际上不会得到 HostRoot。
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
// 要访问 HostRoot,需要传入它的子节点。来指示这一点。
// The same thing applies to Suspense and Activity boundaries.
// 同样,这也适用于 Suspense 和 Activity 边界。
export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
let targetInst: void | Fiber;
if (enableInternalInstanceMap) {
targetInst = internalInstanceMap.get(targetNode as any as InstanceUnion);
} else {
targetInst = (targetNode as any)[internalInstanceKey];
}
if (targetInst) {
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
// 不要在这里返回 HostRoot、SuspenseComponent 或 ActivityComponent。
return targetInst;
}
// If the direct event target isn't a React owned DOM node, we need to look
// to see if one of its parents is a React owned DOM node.
// 如果直接的事件目标不是 React 拥有的 DOM 节点,我们需要查看它的父节点是否是
// React 拥有的 DOM 节点。
let parentNode = targetNode.parentNode;
while (parentNode) {
// We'll check if this is a container root that could include
// React nodes in the future. We need to check this first because
// if we're a child of a dehydrated container, we need to first
// find that inner container before moving on to finding the parent
// instance. Note that we don't check this field on the targetNode
// itself because the fibers are conceptually between the container
// node and the first child. It isn't surrounding the container node.
// If it's not a container, we check if it's an instance.
//
// 我们将检查这是否是一个容器根节点,未来可能会包含 React 节点。我们需要首先进行这个
// 检查,因为如果我们是一个脱水容器的子节点,我们需要先找到那个内部容器,然后再去寻找
// 父实例。注意,我们不会在 targetNode 本身上检查这个字段,因为 fiber 在概念上位于
// 容器节点和第一个子节点之间,而不是包围容器节点。如果它不是容器,我们会检查它是否是
// 一个实例。
if (enableInternalInstanceMap) {
targetInst =
(parentNode as any)[internalContainerInstanceKey] ||
internalInstanceMap.get(parentNode as any as InstanceUnion);
} else {
targetInst =
(parentNode as any)[internalContainerInstanceKey] ||
(parentNode as any)[internalInstanceKey];
}
if (targetInst) {
// Since this wasn't the direct target of the event, we might have
// stepped past dehydrated DOM nodes to get here. However they could
// also have been non-React nodes. We need to answer which one.
// 由于这不是事件的直接目标,我们可能是跳过了已脱水的 DOM 节点才到达这里的。
// 不过它们也可能是非 React 节点。我们需要弄清楚是哪一种。
// If we the instance doesn't have any children, then there can't be
// a nested suspense boundary within it. So we can use this as a fast
// bailout. Most of the time, when people add non-React children to
// the tree, it is using a ref to a child-less DOM node.
// Normally we'd only need to check one of the fibers because if it
// has ever gone from having children to deleting them or vice versa
// it would have deleted the dehydrated boundary nested inside already.
// 如果这个实例没有任何子节点,那么它内部就不可能有嵌套的 suspense 边界。所以我们
// 可以把它作为一个快速退出的判断。大多数时候,当人们向树中添加非 React 子节点时,
// 都是通过引用一个没有子节点的 DOM 节点来实现的。
// 通常我们只需要检查其中一个 fiber,因为如果它曾经从有子节点变为删除子节点,
// 或者反过来,它已经会删除内部嵌套的脱水边界了。
// However, since the HostRoot starts out with an alternate it might
// have one on the alternate so we need to check in case this was a
// root.
// 但是,由于 HostRoot 最初有一个备用副本,它可能在备用副本上也有一个,所以我们需
// 要检查,以防这是一个根节点。
const alternate = targetInst.alternate;
if (
targetInst.child !== null ||
(alternate !== null && alternate.child !== null)
) {
// Next we need to figure out if the node that skipped past is
// nested within a dehydrated boundary and if so, which one.
// 接下来我们需要弄清楚被跳过的节点是否嵌套在已脱水的边界内,如果是的话,是哪个边界。
let hydrationInstance = getParentHydrationBoundary(targetNode);
while (hydrationInstance !== null) {
// We found a suspense instance. That means that we haven't
// hydrated it yet. Even though we leave the comments in the
// DOM after hydrating, and there are boundaries in the DOM
// that could already be hydrated, we wouldn't have found them
// through this pass since if the target is hydrated it would
// have had an internalInstanceKey on it.
// Let's get the fiber associated with the SuspenseComponent
// as the deepest instance.
// 我们发现了一个 Suspense 实例。这意味着我们还没有对其进行 hydration。即
// 使在 hydration 之后我们在 DOM 中保留了这些注释,并且 DOM 中可能已经有边
// 界被 hydration,我们也无法通过这次遍历找到它们,因为如果目标已经被
// hydration,它上面会有一个 internalInstanceKey。
// 让我们获取与该 SuspenseComponent 关联的 fiber,作为最深的实例。
const targetFiber = enableInternalInstanceMap
? internalInstanceMap.get(hydrationInstance)
: hydrationInstance[internalInstanceKey];
if (targetFiber) {
return targetFiber;
}
// If we don't find a Fiber on the comment, it might be because
// we haven't gotten to hydrate it yet. There might still be a
// parent boundary that hasn't above this one so we need to find
// the outer most that is known.
// 如果我们在评论中找不到 Fiber,可能是因为
// 我们还没有对其进行 hydration。可能还有一个
// 上层边界尚未处理,所以我们需要找到
// 已知的最外层边界。
hydrationInstance = getParentHydrationBoundary(hydrationInstance);
// If we don't find one, then that should mean that the parent
// host component also hasn't hydrated yet. We can return it
// below since it will bail out on the isMounted check later.
// 如果我们找不到一个,那应该意味着父级宿主组件还没有完成 hydration。我们可
// 以在下面返回它,因为它稍后会在 isMounted 检查时退出。
}
}
return targetInst;
}
targetNode = parentNode;
parentNode = targetNode.parentNode;
}
return null;
}
八、从节点获取实例
/**
* Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
* instance, or null if the node was not rendered by this React.
* * 给定一个 DOM 节点,返回 ReactDOMComponent 或 ReactDOMTextComponent 实例,如果该节点不是由此 React 渲染的,则返回 null。
*/
export function getInstanceFromNode(node: Node): Fiber | null {
let inst: void | null | Fiber;
if (enableInternalInstanceMap) {
inst =
internalInstanceMap.get(node as any as InstanceUnion) ||
(node as any)[internalContainerInstanceKey];
} else {
inst =
(node as any)[internalInstanceKey] ||
(node as any)[internalContainerInstanceKey];
}
if (inst) {
const tag = inst.tag;
if (
tag === HostComponent ||
tag === HostText ||
tag === SuspenseComponent ||
tag === ActivityComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostRoot
) {
return inst;
} else {
return null;
}
}
return null;
}
九、从实例获取节点
/**
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
* DOM node.
* 给定一个 ReactDOMComponent 或 ReactDOMTextComponent,返回对应的 DOM 节点。
*/
export function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
const tag = inst.tag;
if (
tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostText
) {
// In Fiber this, is just the state node right now. We assume it will be
// a host component or host text.
// 在 Fiber 中,这现在只是状态节点。我们假设它将是一个宿主组件或宿主文本。
return inst.stateNode;
}
// Without this first invariant, passing a non-DOM-component triggers the next
// invariant for a missing parent, which is super confusing.
// 没有这个初始不变式,传递一个非 DOM 组件会触发下一个缺少父组件的不变式,这会非常让人困惑。
throw new Error('getNodeFromInstance: Invalid argument.');
}
十、从节点获取 Fiber 当前属性
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function getFiberCurrentPropsFromNode(
node:
| Container
| Instance
| TextInstance
| SuspenseInstance
| ActivityInstance,
): Props | null {
if (enableInternalInstanceMap) {
return internalPropsMap.get(node) || null;
}
return (node as any)[internalPropsKey] || null;
}
十一、更新 Fiber 属性
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function updateFiberProps(node: Instance, props: Props): void {
if (enableInternalInstanceMap) {
internalPropsMap.set(node, props);
return;
}
(node as any)[internalPropsKey] = props;
}
十二、获取事件监听器集合
export function getEventListenerSet(node: EventTarget): Set<string> {
let elementListenerSet: Set<string> | void = (node as any)[
internalEventHandlersKey
];
if (elementListenerSet === undefined) {
elementListenerSet = (node as any)[internalEventHandlersKey] = new Set();
}
return elementListenerSet;
}
十三、从作用域实例获取 Fiber
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function getFiberFromScopeInstance(
scope: ReactScopeInstance,
): null | Fiber {
if (enableScopeAPI) {
if (enableInternalInstanceMap) {
return internalInstanceMap.get(scope as any as InstanceUnion) || null;
}
return (scope as any)[internalInstanceKey] || null;
}
return null;
}
十四、设置事件处理程序监听器
export function setEventHandlerListeners(
scope: EventTarget | ReactScopeInstance,
listeners: Set<ReactDOMEventHandleListener>,
): void {
(scope as any)[internalEventHandlerListenersKey] = listeners;
}
十五、获取事件处理程序监听器
export function getEventHandlerListeners(
scope: EventTarget | ReactScopeInstance,
): null | Set<ReactDOMEventHandleListener> {
return (scope as any)[internalEventHandlerListenersKey] || null;
}
十六、将事件处理程序添加到目标
export function addEventHandleToTarget(
target: EventTarget | ReactScopeInstance,
eventHandle: ReactDOMEventHandle,
): void {
let eventHandles = (target as any)[internalEventHandlesSetKey];
if (eventHandles === undefined) {
eventHandles = (target as any)[internalEventHandlesSetKey] = new Set();
}
eventHandles.add(eventHandle);
}
十七、目标是否有事件处理程序
export function doesTargetHaveEventHandle(
target: EventTarget | ReactScopeInstance,
eventHandle: ReactDOMEventHandle,
): boolean {
const eventHandles = (target as any)[internalEventHandlesSetKey];
if (eventHandles === undefined) {
return false;
}
return eventHandles.has(eventHandle);
}
十八、从根获取资源
export function getResourcesFromRoot(root: HoistableRoot): RootResources {
let resources = (root as any)[internalRootNodeResourcesKey];
if (!resources) {
resources = (root as any)[internalRootNodeResourcesKey] = {
hoistableStyles: new Map(),
hoistableScripts: new Map(),
};
}
return resources;
}
十九、是否可提升标记
export function isMarkedHoistable(node: Node): boolean {
return !!(node as any)[internalHoistableMarker];
}
二十、将节点标记为可提升
export function markNodeAsHoistable(node: Node) {
(node as any)[internalHoistableMarker] = true;
}
廿一、获取滚动结束计时器
export function getScrollEndTimer(node: EventTarget): ?TimeoutID {
return (node as any)[internalScrollTimer];
}
廿二、设置滚动结束定时器
export function setScrollEndTimer(node: EventTarget, timer: TimeoutID): void {
(node as any)[internalScrollTimer] = timer;
}
廿三、清除滚动结束计时器
export function clearScrollEndTimer(node: EventTarget): void {
(node as any)[internalScrollTimer] = undefined;
}
廿四、是否为拥有的实例
备注
enableInternalInstanceMap由 ReactFeatureFlags#enableInternalInstanceMap 提供
export function isOwnedInstance(node: Node): boolean {
if (enableInternalInstanceMap) {
return !!(
(node as any)[internalHoistableMarker] ||
internalInstanceMap.has(node as any)
);
}
return !!(
(node as any)[internalHoistableMarker] || (node as any)[internalInstanceKey]
);
}
廿五、常量
1. 随机键
备注
源码中 43 - 52 行
// 随机键
const randomKey = Math.random().toString(36).slice(2);
// 内部实例键
const internalInstanceKey = '__reactFiber$' + randomKey;
// 内部属性键
const internalPropsKey = '__reactProps$' + randomKey;
// 内部容器实例键
const internalContainerInstanceKey = '__reactContainer$' + randomKey;
// 内部事件处理程序键
const internalEventHandlersKey = '__reactEvents$' + randomKey;
// 内部事件处理程序监听器键
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
// 内部事件句柄设置键
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
// 内部根节点资源键
const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
// 内部可吊运标记
const internalHoistableMarker = '__reactMarker$' + randomKey;
// 内部滚动定时器
const internalScrollTimer = '__reactScroll$' + randomKey;
2. 可能弱映射
备注
源码中 62 - 68 行
// 可能弱映射
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
// 内部实例映射
const internalInstanceMap:
| WeakMap<InstanceUnion, Fiber>
| Map<InstanceUnion, Fiber> = new PossiblyWeakMap();
// 内部属性映射
const internalPropsMap:
| WeakMap<InstanceUnion, Props>
| Map<InstanceUnion, Props> = new PossiblyWeakMap();
廿六、类型
1. 实例联合
type InstanceUnion =
| Instance
| TextInstance
| SuspenseInstance
| ActivityInstance
| ReactScopeInstance
| Container;