Skip to content

Commit c08a24a

Browse files
committed
Track namespaces instead of creating instances early
While we might want to create instance in the begin phase, we shouldn't let DOM guide reconciler design. Instead, we are adding a new concept of "host context". In case of ReactDOMFiber, it's just the current namespace. We are keeping a stack of host context values, ignoring those that are referentially equal. The renderer receives the parent context and type, and can return a new context.
1 parent 2099a2a commit c08a24a

File tree

10 files changed

+164
-158
lines changed

10 files changed

+164
-158
lines changed

src/renderers/dom/fiber/ReactDOMFiber.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var warning = require('warning');
3333

3434
var {
3535
createElement,
36-
isNewHostContainer,
36+
getNamespace,
3737
setInitialProperties,
3838
updateProperties,
3939
} = ReactDOMFiberComponent;
@@ -61,8 +61,8 @@ let selectionInformation : ?mixed = null;
6161

6262
var DOMRenderer = ReactFiberReconciler({
6363

64-
isContainerType(type : string) {
65-
return isNewHostContainer(type);
64+
getHostContext(parentHostContext : string | null, type : string) {
65+
return getNamespace(parentHostContext, type);
6666
},
6767

6868
prepareForCommit() : void {
@@ -82,10 +82,10 @@ var DOMRenderer = ReactFiberReconciler({
8282
type : string,
8383
props : Props,
8484
rootContainerInstance : Container,
85-
containerInstance : Instance | Container,
85+
hostContext : string | null,
8686
internalInstanceHandle : Object,
8787
) : Instance {
88-
const domElement : Instance = createElement(type, props, rootContainerInstance, containerInstance);
88+
const domElement : Instance = createElement(type, props, rootContainerInstance, hostContext);
8989
precacheFiberNode(internalInstanceHandle, domElement);
9090
return domElement;
9191
},

src/renderers/dom/fiber/ReactDOMFiberComponent.js

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ var CHILDREN = 'children';
4646
var STYLE = 'style';
4747
var HTML = '__html';
4848

49+
var {
50+
svg: SVG_NAMESPACE,
51+
math: MATH_NAMESPACE,
52+
} = DOMNamespaces;
53+
4954
// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
5055
var DOC_FRAGMENT_TYPE = 11;
5156

@@ -452,49 +457,38 @@ function updateDOMProperties(
452457
}
453458

454459
var ReactDOMFiberComponent = {
455-
456-
// TODO: Does this need to check the current namespace? In case these tags
457-
// happen to be valid in some other namespace.
458-
isNewHostContainer(type : string) {
459-
// We don't need convert user-provided "type" to a lowercase "tag" because
460-
// both cases we're comparing against are SVG tags, which is case sensitive.
461-
return (
462-
type === 'svg' ||
463-
type === 'foreignObject'
464-
);
460+
getNamespace(parentNamespace : string | null, type : string) : string | null {
461+
if (parentNamespace == null) {
462+
switch (type) {
463+
case 'svg':
464+
return SVG_NAMESPACE;
465+
case 'math':
466+
return MATH_NAMESPACE;
467+
default:
468+
return null;
469+
}
470+
}
471+
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
472+
return null;
473+
}
474+
return parentNamespace;
465475
},
466476

467477
createElement(
468478
type : string,
469479
props : Object,
470480
rootContainerElement : Element,
471-
containerElement : Element
481+
namespaceURI : string | null
472482
) : Element {
473483
validateDangerousTag(type);
474484
// TODO:
475485
// const tag = type.toLowerCase(); Do we need to apply lower case only on non-custom elements?
476486

477487
// We create tags in the namespace of their parent container, except HTML
478488
// tags get no namespace.
479-
var namespaceURI = containerElement.namespaceURI;
480-
if (namespaceURI == null ||
481-
namespaceURI === DOMNamespaces.svg &&
482-
// We don't need convert to lowercase because SVG is case sensitive:
483-
containerElement.tagName === 'foreignObject') {
484-
namespaceURI = DOMNamespaces.html;
485-
}
486-
if (namespaceURI === DOMNamespaces.html) {
487-
// We don't need convert to lowercase because SVG is case sensitive.
488-
if (type === 'svg') {
489-
namespaceURI = DOMNamespaces.svg;
490-
} else if (type === 'math') {
491-
namespaceURI = DOMNamespaces.mathml;
492-
}
493-
}
494-
495489
var ownerDocument = rootContainerElement.ownerDocument;
496490
var domElement : Element;
497-
if (namespaceURI === DOMNamespaces.html) {
491+
if (namespaceURI == null) {
498492
const tag = type.toLowerCase();
499493
if (tag === 'script') {
500494
// Create the script via .innerHTML so its "parser-inserted" flag is

src/renderers/noop/ReactNoop.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ var instanceCounter = 0;
4040

4141
var NoopRenderer = ReactFiberReconciler({
4242

43-
isContainerType() {
44-
return false;
43+
getHostContext() {
44+
return null;
4545
},
4646

4747
createInstance(type : string, props : Props) : Instance {

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,15 @@ var {
5858
var ReactCurrentOwner = require('ReactCurrentOwner');
5959
var ReactFiberClassComponent = require('ReactFiberClassComponent');
6060

61-
module.exports = function<T, P, I, TI, C>(
62-
config : HostConfig<T, P, I, TI, C>,
63-
hostContext : HostContext<I, C>,
61+
module.exports = function<T, P, I, TI, C, CX>(
62+
config : HostConfig<T, P, I, TI, C, CX>,
63+
hostContext : HostContext<C, CX>,
6464
scheduleUpdate : (fiber: Fiber) => void
6565
) {
6666

6767
const {
68-
createInstance,
69-
createTextInstance,
70-
isContainerType,
71-
} = config;
72-
73-
const {
74-
getHostContainerOnStack,
75-
getRootHostContainerOnStack,
76-
pushHostContainer,
68+
setRootHostContainer,
69+
maybePushHostContext,
7770
saveHostContextToPortal,
7871
} = hostContext;
7972

@@ -233,22 +226,6 @@ module.exports = function<T, P, I, TI, C>(
233226
// avoids allocating another HostText fiber and traversing it.
234227
nextChildren = null;
235228
}
236-
if (!current && workInProgress.stateNode == null) {
237-
const newProps = workInProgress.pendingProps;
238-
const containerInstance = getHostContainerOnStack();
239-
const rootContainerInstance = getRootHostContainerOnStack();
240-
if (rootContainerInstance == null) {
241-
throw new Error('Expected to find a root instance on the host stack.');
242-
}
243-
const instance = createInstance(
244-
workInProgress.type,
245-
newProps,
246-
rootContainerInstance,
247-
containerInstance,
248-
workInProgress
249-
);
250-
workInProgress.stateNode = instance;
251-
}
252229
if (workInProgress.pendingProps.hidden &&
253230
workInProgress.pendingWorkPriority !== OffscreenPriority) {
254231
// If this host component is hidden, we can bail out on the children.
@@ -285,9 +262,7 @@ module.exports = function<T, P, I, TI, C>(
285262
// Abort and don't process children yet.
286263
return null;
287264
} else {
288-
if (isContainerType(workInProgress.type)) {
289-
pushHostContainer(workInProgress.stateNode);
290-
}
265+
maybePushHostContext(workInProgress);
291266
reconcileChildren(current, workInProgress, nextChildren);
292267
return workInProgress.child;
293268
}
@@ -422,9 +397,7 @@ module.exports = function<T, P, I, TI, C>(
422397

423398
// Put context on the stack because we will work on children
424399
if (isHostComponent) {
425-
if (isContainerType(workInProgress.type)) {
426-
pushHostContainer(workInProgress.stateNode);
427-
}
400+
maybePushHostContext(workInProgress);
428401
} else {
429402
switch (workInProgress.tag) {
430403
case ClassComponent:
@@ -433,7 +406,7 @@ module.exports = function<T, P, I, TI, C>(
433406
}
434407
break;
435408
case HostContainer:
436-
pushHostContainer(workInProgress.stateNode.containerInfo);
409+
setRootHostContainer(workInProgress.stateNode.containerInfo);
437410
break;
438411
case Portal:
439412
saveHostContextToPortal(workInProgress);
@@ -499,7 +472,7 @@ module.exports = function<T, P, I, TI, C>(
499472
} else {
500473
pushTopLevelContextObject(root.context, false);
501474
}
502-
pushHostContainer(workInProgress.stateNode.containerInfo);
475+
setRootHostContainer(workInProgress.stateNode.containerInfo);
503476
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
504477
// A yield component is just a placeholder, we can just run through the
505478
// next one immediately.
@@ -508,15 +481,8 @@ module.exports = function<T, P, I, TI, C>(
508481
case HostComponent:
509482
return updateHostComponent(current, workInProgress);
510483
case HostText:
511-
const newText = workInProgress.pendingProps;
512-
if (typeof newText !== 'string') {
513-
throw new Error('We must have new props for new mounts.');
514-
}
515-
if (!current) {
516-
const textInstance = createTextInstance(newText, workInProgress);
517-
workInProgress.stateNode = textInstance;
518-
}
519-
// This is terminal. We'll do the completion step immediately after.
484+
// Nothing to do here. This is terminal. We'll do the completion step
485+
// immediately after.
520486
return null;
521487
case CoroutineHandlerPhase:
522488
// This is a restart. Reset the tag to the initial phase.

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ var {
3333
Callback,
3434
} = require('ReactTypeOfSideEffect');
3535

36-
module.exports = function<T, P, I, TI, C>(
37-
config : HostConfig<T, P, I, TI, C>,
38-
hostContext : HostContext<I, C>,
36+
module.exports = function<T, P, I, TI, C, CX>(
37+
config : HostConfig<T, P, I, TI, C, CX>,
38+
hostContext : HostContext<C, CX>,
3939
trapError : (failedFiber : Fiber, error: Error, isUnmounting : boolean) => void
4040
) {
4141

@@ -48,7 +48,7 @@ module.exports = function<T, P, I, TI, C>(
4848
} = config;
4949

5050
const {
51-
getRootHostContainerOnStack,
51+
getRootHostContainer,
5252
} = hostContext;
5353

5454
function detachRef(current : Fiber) {
@@ -310,10 +310,7 @@ module.exports = function<T, P, I, TI, C>(
310310
// Commit the work prepared earlier.
311311
const newProps = finishedWork.memoizedProps;
312312
const oldProps = current.memoizedProps;
313-
const rootContainerInstance = getRootHostContainerOnStack();
314-
if (rootContainerInstance == null) {
315-
throw new Error('Expected to find a root instance on the host stack.');
316-
}
313+
const rootContainerInstance = getRootHostContainer();
317314
commitUpdate(instance, oldProps, newProps, rootContainerInstance, finishedWork);
318315
}
319316
detachRefIfNeeded(current, finishedWork);

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,22 @@ var {
4444
Callback,
4545
} = ReactTypeOfSideEffect;
4646

47-
module.exports = function<T, P, I, TI, C>(
48-
config : HostConfig<T, P, I, TI, C>,
49-
hostContext : HostContext<I, C>,
47+
module.exports = function<T, P, I, TI, C, CX>(
48+
config : HostConfig<T, P, I, TI, C, CX>,
49+
hostContext : HostContext<C, CX>,
5050
) {
5151
const {
52+
createInstance,
53+
createTextInstance,
5254
appendInitialChild,
5355
finalizeInitialChildren,
5456
prepareUpdate,
5557
} = config;
5658

5759
const {
58-
getRootHostContainerOnStack,
59-
getHostContainerOnStack,
60-
popHostContainer,
60+
getRootHostContainer,
61+
maybePopHostContext,
62+
getCurrentHostContext,
6163
restoreHostContextFromPortal,
6264
} = hostContext;
6365

@@ -212,15 +214,8 @@ module.exports = function<T, P, I, TI, C>(
212214
return null;
213215
}
214216
case HostComponent:
215-
const instance : I = workInProgress.stateNode;
216-
if (!instance) {
217-
throw new Error('Expected host instance to be created in begin phase.');
218-
}
219-
if (instance === getHostContainerOnStack()) {
220-
popHostContainer();
221-
}
222217
let newProps = workInProgress.pendingProps;
223-
if (current) {
218+
if (current && workInProgress.stateNode != null) {
224219
// If we have an alternate, that means this is an update and we need to
225220
// schedule a side-effect to do the updates.
226221
const oldProps = current.memoizedProps;
@@ -231,6 +226,7 @@ module.exports = function<T, P, I, TI, C>(
231226
if (!newProps) {
232227
newProps = workInProgress.memoizedProps || oldProps;
233228
}
229+
const instance : I = workInProgress.stateNode;
234230
if (prepareUpdate(instance, oldProps, newProps)) {
235231
// This returns true if there was something to update.
236232
markUpdate(workInProgress);
@@ -245,29 +241,35 @@ module.exports = function<T, P, I, TI, C>(
245241
}
246242
}
247243

248-
const rootContainerInstance = getRootHostContainerOnStack();
249-
if (rootContainerInstance == null) {
250-
throw new Error('Expected to find a root instance on the host stack.');
251-
}
252-
// TODO: Keep the instance on a context "stack" as the parent.
253-
// Then append children as we go in beginWork or completeWork
254-
// depending on we want to add then top->down or bottom->up.
255-
// Top->down is faster in IE11.
256-
// Finally, finalizeInitialChildren here in completeWork.
244+
const rootContainerInstance = getRootHostContainer();
245+
const currentHostContext = getCurrentHostContext();
246+
// TODO: Move createInstance to beginWork and keep it on a context
247+
// "stack" as the parent. Then append children as we go in beginWork
248+
// or completeWork depending on we want to add then top->down or
249+
// bottom->up. Top->down is faster in IE11.
250+
const instance = createInstance(
251+
workInProgress.type,
252+
newProps,
253+
rootContainerInstance,
254+
currentHostContext,
255+
workInProgress
256+
);
257257
appendAllChildren(instance, workInProgress);
258258
finalizeInitialChildren(instance, newProps, rootContainerInstance);
259259

260+
workInProgress.stateNode = instance;
260261
if (workInProgress.ref) {
261262
// If there is a ref on a host node we need to schedule a callback
262263
markUpdate(workInProgress);
263264
}
264265
}
266+
maybePopHostContext(workInProgress);
265267
workInProgress.memoizedProps = newProps;
266268
return null;
267269
case HostText:
268270
let newText = workInProgress.pendingProps;
269271
if (current && workInProgress.stateNode != null) {
270-
const oldText = current.memoizedProps;
272+
const oldText = current.memoizedProps;
271273
if (newText === null) {
272274
// If this was a bail out we need to fall back to memoized text.
273275
// This works the same way as HostComponent.
@@ -281,6 +283,17 @@ module.exports = function<T, P, I, TI, C>(
281283
if (oldText !== newText) {
282284
markUpdate(workInProgress);
283285
}
286+
} else {
287+
if (typeof newText !== 'string') {
288+
if (workInProgress.stateNode === null) {
289+
throw new Error('We must have new props for new mounts.');
290+
} else {
291+
// This can happen when we abort work.
292+
return null;
293+
}
294+
}
295+
const textInstance = createTextInstance(newText, workInProgress);
296+
workInProgress.stateNode = textInstance;
284297
}
285298
workInProgress.memoizedProps = newText;
286299
return null;

0 commit comments

Comments
 (0)