mirror of
https://github.com/facebook/react.git
synced 2026-02-26 18:58:05 +00:00
Support comment node as a mount point (#9835)
This means you don't need an extra wrapper div for each component root you need. Rendered stuff is inserted before the comment you pass in.
This commit is contained in:
@@ -1433,6 +1433,7 @@ src/renderers/dom/shared/__tests__/ReactMount-test.js
|
||||
* should warn if the unmounted node was rendered by another copy of React
|
||||
* passes the correct callback context
|
||||
* initial mount is sync inside batchedUpdates, but task work is deferred until the end of the batch
|
||||
* renders at a comment node
|
||||
|
||||
src/renderers/dom/shared/__tests__/ReactMountDestruction-test.js
|
||||
* should destroy a react root upon request
|
||||
|
||||
@@ -33,6 +33,8 @@ var {isValidElement} = require('react');
|
||||
var {injectInternals} = require('ReactFiberDevToolsHook');
|
||||
var {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} = require('HTMLNodeType');
|
||||
@@ -99,7 +101,9 @@ function isValidContainer(node) {
|
||||
return !!(node &&
|
||||
(node.nodeType === ELEMENT_NODE ||
|
||||
node.nodeType === DOCUMENT_NODE ||
|
||||
node.nodeType === DOCUMENT_FRAGMENT_NODE));
|
||||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
|
||||
(node.nodeType === COMMENT_NODE &&
|
||||
node.nodeValue === ' react-mount-point-unstable ')));
|
||||
}
|
||||
|
||||
function getReactRootElementInContainer(container: any) {
|
||||
@@ -141,8 +145,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
let root = (rootContainerInstance: any).documentElement;
|
||||
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
|
||||
} else {
|
||||
const ownNamespace = (rootContainerInstance: any).namespaceURI || null;
|
||||
type = (rootContainerInstance: any).tagName;
|
||||
const container: any = rootContainerInstance.nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
const ownNamespace = container.namespaceURI || null;
|
||||
type = container.tagName;
|
||||
namespace = getChildNamespace(ownNamespace, type);
|
||||
}
|
||||
if (__DEV__) {
|
||||
@@ -354,7 +361,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
container.appendChild(child);
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, container);
|
||||
} else {
|
||||
container.appendChild(child);
|
||||
}
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
@@ -370,7 +381,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
container.insertBefore(child, beforeChild);
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
} else {
|
||||
container.insertBefore(child, beforeChild);
|
||||
}
|
||||
},
|
||||
|
||||
removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
|
||||
@@ -381,7 +396,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
container.removeChild(child);
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
container.removeChild(child);
|
||||
}
|
||||
},
|
||||
|
||||
canHydrateInstance(
|
||||
@@ -389,7 +408,10 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
type: string,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return instance.nodeType === 1 && type === instance.nodeName.toLowerCase();
|
||||
return (
|
||||
instance.nodeType === ELEMENT_NODE &&
|
||||
type === instance.nodeName.toLowerCase()
|
||||
);
|
||||
},
|
||||
|
||||
canHydrateTextInstance(
|
||||
@@ -400,7 +422,7 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return false;
|
||||
}
|
||||
return instance.nodeType === 3;
|
||||
return instance.nodeType === TEXT_NODE;
|
||||
},
|
||||
|
||||
getNextHydratableSibling(
|
||||
@@ -408,7 +430,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (node && node.nodeType !== 1 && node.nodeType !== 3) {
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return (node: any);
|
||||
@@ -419,7 +445,11 @@ var DOMRenderer = ReactFiberReconciler({
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (next && next.nodeType !== 1 && next.nodeType !== 3) {
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
return (next: any);
|
||||
@@ -483,9 +513,9 @@ function renderSubtreeIntoContainer(
|
||||
);
|
||||
|
||||
warning(
|
||||
container.nodeType !== 1 ||
|
||||
!container.tagName ||
|
||||
container.tagName.toUpperCase() !== 'BODY',
|
||||
container.nodeType !== ELEMENT_NODE ||
|
||||
!((container: any): Element).tagName ||
|
||||
((container: any): Element).tagName.toUpperCase() !== 'BODY',
|
||||
'render(): Rendering components directly into document.body is ' +
|
||||
'discouraged, since its children are often manipulated by third-party ' +
|
||||
'scripts and browser extensions. This may lead to subtle ' +
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMServer;
|
||||
@@ -345,4 +349,45 @@ describe('ReactMount', () => {
|
||||
expect(container1.textContent).toEqual('2');
|
||||
expect(container2.textContent).toEqual('a!');
|
||||
});
|
||||
|
||||
if (ReactDOMFeatureFlags.useFiber) {
|
||||
describe('mount point is a comment node', () => {
|
||||
let containerDiv;
|
||||
let mountPoint;
|
||||
|
||||
beforeEach(() => {
|
||||
const ReactFeatureFlags = require('ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableNewFiberFeatures = false;
|
||||
|
||||
containerDiv = document.createElement('div');
|
||||
containerDiv.innerHTML = 'A<!-- react-mount-point-unstable -->B';
|
||||
mountPoint = containerDiv.childNodes[1];
|
||||
invariant(mountPoint.nodeType === 8, 'Expected comment');
|
||||
});
|
||||
|
||||
it('renders at a comment node', () => {
|
||||
function Char(props) {
|
||||
return props.children;
|
||||
}
|
||||
function list(chars) {
|
||||
return chars.split('').map(c => <Char key={c}>{c}</Char>);
|
||||
}
|
||||
|
||||
ReactDOM.render(list('aeiou'), mountPoint);
|
||||
expect(containerDiv.innerHTML).toBe(
|
||||
'Aaeiou<!-- react-mount-point-unstable -->B',
|
||||
);
|
||||
|
||||
ReactDOM.render(list('yea'), mountPoint);
|
||||
expect(containerDiv.innerHTML).toBe(
|
||||
'Ayea<!-- react-mount-point-unstable -->B',
|
||||
);
|
||||
|
||||
ReactDOM.render(list(''), mountPoint);
|
||||
expect(containerDiv.innerHTML).toBe(
|
||||
'A<!-- react-mount-point-unstable -->B',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user