packages/react-dom/src/client/ReactDOM.js
ReactDOM
const ReactDOM: Object = {
createPortal,
findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
},
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
},
unstable_renderSubtreeIntoContainer(
parentComponent: React$Component<any, any>,
element: React$Element<any>,
containerNode: DOMContainer,
callback: ?Function,
) {
},
unmountComponentAtNode(container: DOMContainer) {
},
// Temporary alias since we already shipped React 16 RC with it.
// TODO: remove in React 17.
unstable_createPortal(...args) {
},
unstable_batchedUpdates: batchedUpdates,
unstable_interactiveUpdates: interactiveUpdates,
flushSync: flushSync,
unstable_createRoot: createRoot,
unstable_flushControlled: flushControlled,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// Keep in sync with ReactDOMUnstableNativeDependencies.js
// and ReactTestUtils.js. This is an array for better minification.
Events: [
getInstanceFromNode,
getNodeFromInstance,
getFiberCurrentPropsFromNode,
EventPluginHubInjection.injectEventPluginsByName,
eventNameDispatchConfigs,
accumulateTwoPhaseDispatches,
accumulateDirectDispatches,
enqueueStateRestore,
restoreStateIfNeeded,
dispatchEvent,
runEventsInBatch,
],
},
};
复制代码
createPortal
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 createPortalImpl(children, container, null, key);
}
复制代码
createPortal
调用 createPortalImpl
方法返回一个 ReactPortal
类型的对象。
findDOMNode
findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
// do something
}
if (componentOrElement == null) {
return null;
}
// nodeType 为 1 时,说明是一个 dom 元素
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
return (componentOrElement: any);
}
if (__DEV__) {
// do something
}
return findHostInstance(componentOrElement);
},
复制代码
findDOMNode
方法接收一个唯一的参数:
- 当参数为
null
或undefined
时返回 null - 当参数为一个 dom 元素时,直接返回这个 dom 元素
- 当参数为一个组件时,调用
findHostInstance
获取组件对应的 dom 元素
hydrate
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
// do something
}
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
}
复制代码
render
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
// do something
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
复制代码
unmountComponentAtNode
unmountComponentAtNode(container: DOMContainer) {
invariant(
isValidContainer(container),
'unmountComponentAtNode(...): Target container is not a DOM element.',
);
if (__DEV__) {
// do something
}
if (container._reactRootContainer) {
if (__DEV__) {
// do something
}
// Unmount should not be batched.
unbatchedUpdates(() => {
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
container._reactRootContainer = null;
});
});
// If you call unmountComponentAtNode twice in quick succession, you'll
// get `true` twice. That's probably fine?
return true;
} else {
if (__DEV__) {
// do something
}
return false;
}
},
复制代码
其他
Root
type Root = {
render(children: ReactNodeList, callback: ?() => mixed): Work,
unmount(callback: ?() => mixed): Work,
legacy_renderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work,
createBatch(): Batch,
_internalRoot: FiberRoot,
};
复制代码
ReactWork
function ReactWork() {
this._callbacks = null;
this._didCommit = false;
// TODO: Avoid need to bind by replacing callbacks in the update queue with
// list of Work objects.
this._onCommit = this._onCommit.bind(this);
}
ReactWork.prototype.then = function(onCommit: () => mixed): void {
if (this._didCommit) {
onCommit();
return;
}
let callbacks = this._callbacks;
if (callbacks === null) {
callbacks = this._callbacks = [];
}
callbacks.push(onCommit);
};
ReactWork.prototype._onCommit = function(): void {
if (this._didCommit) {
return;
}
this._didCommit = true;
const callbacks = this._callbacks;
if (callbacks === null) {
return;
}
// TODO: Error handling.
for (let i = 0; i < callbacks.length; i++) {
const callback = callbacks[i];
invariant(
typeof callback === 'function',
'Invalid argument passed as callback. Expected a function. Instead ' +
'received: %s',
callback,
);
callback();
}
};
复制代码
ReactRoot
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = 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);
}
updateContainer(children, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.unmount = function(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);
}
updateContainer(null, root, null, work._onCommit);
return work;
};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
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);
}
updateContainer(children, root, parentComponent, work._onCommit);
return work;
};
ReactRoot.prototype.createBatch = function(): Batch {
const batch = new ReactBatch(this);
const expirationTime = batch._expirationTime;
const internalRoot = this._internalRoot;
const firstBatch = internalRoot.firstBatch;
if (firstBatch === null) {
internalRoot.firstBatch = batch;
batch._next = null;
} else {
// Insert sorted by expiration time then insertion order
let insertAfter = null;
let insertBefore = firstBatch;
while (
insertBefore !== null &&
insertBefore._expirationTime >= expirationTime
) {
insertAfter = insertBefore;
insertBefore = insertBefore._next;
}
batch._next = insertBefore;
if (insertAfter !== null) {
insertAfter._next = batch;
}
}
return batch;
};
复制代码
getReactRootElementInContainer
从容器中获取 React 根元素。
function getReactRootElementInContainer(container: any) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
复制代码
getReactRootElementInContainer
方法会根据容器的 nodeType
去获取它的子节点:
-
容器为
domcument
时,通过domcument.documentElement
获取 -
容器为非
domcument
时,通过container.firstChild
获取它的第一个子元素。因为挂载的时候就是一个单一的节点。
shouldHydrateDueToLegacyHeuristic
function shouldHydrateDueToLegacyHeuristic(container) {
// 获取根元素
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
复制代码
满足三个条件时为真:
- 根 dom 节点存在
- 根 dom 节点根 dom 节点为普通的 dom 元素。如
<div id="app"></div>
- 根 dom 节点具有
data-reactroot
属性
legacyCreateRootFromDOMContainer
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)) {
if (__DEV__) {
// do something
}
container.removeChild(rootSibling);
}
}
if (__DEV__) {
// do something
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
复制代码
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
if (__DEV__) {
// do something
}
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
if (!root) {
// 初始挂载
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return getPublicRootInstance(root._internalRoot);
}
复制代码