Skip to content

Commit 7e54ca4

Browse files
committed
Have the renderer refine Hydratable Instance to Instance or Text Instance
Currently we assume that if canHydrateInstance or canHydrateTextInstance returns true, then the types also match up. But we don't tell that to Flow. It just happens to work because `fiber.stateNode` is still `any`. We could potentially use some kind of predicate typing but instead of that I can just return null or instance from the "can" tests. This ensures that the renderer has to do the refinement properly.
1 parent 5eafad4 commit 7e54ca4

7 files changed

Lines changed: 53 additions & 48 deletions

File tree

packages/react-dom/src/client/ReactDOM.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -459,22 +459,27 @@ const DOMRenderer = ReactFiberReconciler({
459459
instance: Instance | TextInstance,
460460
type: string,
461461
props: Props,
462-
): boolean {
463-
return (
464-
instance.nodeType === ELEMENT_NODE &&
465-
type.toLowerCase() === instance.nodeName.toLowerCase()
466-
);
462+
): null | Instance {
463+
if (
464+
instance.nodeType !== ELEMENT_NODE ||
465+
type.toLowerCase() !== instance.nodeName.toLowerCase()
466+
) {
467+
return null;
468+
}
469+
// This has now been refined to an element node.
470+
return ((instance: any): Instance);
467471
},
468472

469473
canHydrateTextInstance(
470474
instance: Instance | TextInstance,
471475
text: string,
472-
): boolean {
473-
if (text === '') {
476+
): null | TextInstance {
477+
if (text === '' || instance.nodeType !== TEXT_NODE) {
474478
// Empty strings are not parsed by HTML so there won't be a correct match here.
475-
return false;
479+
return null;
476480
}
477-
return instance.nodeType === TEXT_NODE;
481+
// This has now been refined to a text node.
482+
return ((instance: any): TextInstance);
478483
},
479484

480485
getNextHydratableSibling(

packages/react-dom/src/client/ReactDOMFiberComponent.js

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
*/
99

1010
// TODO: direct imports like some-package/src/* are bad. Fix me.
11-
import ReactDebugCurrentFiber
12-
from 'react-reconciler/src/ReactDebugCurrentFiber';
11+
import ReactDebugCurrentFiber from 'react-reconciler/src/ReactDebugCurrentFiber';
1312
import {registrationNameModules} from 'events/EventPluginRegistry';
1413
import emptyFunction from 'fbjs/lib/emptyFunction';
1514
import warning from 'fbjs/lib/warning';
@@ -29,15 +28,9 @@ import {getPropertyInfo, shouldSetAttribute} from '../shared/DOMProperty';
2928
import assertValidProps from '../shared/assertValidProps';
3029
import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType';
3130
import isCustomComponent from '../shared/isCustomComponent';
32-
import {
33-
validateProperties as validateARIAProperties,
34-
} from '../shared/ReactDOMInvalidARIAHook';
35-
import {
36-
validateProperties as validateInputProperties,
37-
} from '../shared/ReactDOMNullInputValuePropHook';
38-
import {
39-
validateProperties as validateUnknownProperties,
40-
} from '../shared/ReactDOMUnknownPropertyHook';
31+
import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';
32+
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
33+
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
4134

4235
var {
4336
getCurrentFiberOwnerName,
@@ -86,9 +79,8 @@ if (__DEV__) {
8679
var NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g;
8780

8881
var normalizeMarkupForTextOrAttribute = function(markup: mixed): string {
89-
const markupString = typeof markup === 'string'
90-
? markup
91-
: '' + (markup: any);
82+
const markupString =
83+
typeof markup === 'string' ? markup : '' + (markup: any);
9284
return markupString
9385
.replace(NORMALIZE_NEWLINES_REGEX, '\n')
9486
.replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, '');
@@ -184,12 +176,13 @@ if (__DEV__) {
184176
// re-initializing custom elements if they exist. But this breaks
185177
// how <noscript> is being handled. So we use the same document.
186178
// See the discussion in https://github.com/facebook/react/pull/11157.
187-
var testElement = parent.namespaceURI === HTML_NAMESPACE
188-
? parent.ownerDocument.createElement(parent.tagName)
189-
: parent.ownerDocument.createElementNS(
190-
(parent.namespaceURI: any),
191-
parent.tagName,
192-
);
179+
var testElement =
180+
parent.namespaceURI === HTML_NAMESPACE
181+
? parent.ownerDocument.createElement(parent.tagName)
182+
: parent.ownerDocument.createElementNS(
183+
(parent.namespaceURI: any),
184+
parent.tagName,
185+
);
193186
testElement.innerHTML = html;
194187
return testElement.innerHTML;
195188
};

packages/react-native-renderer/src/ReactNativeComponent.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ import findNumericNodeHandle from './findNumericNodeHandle';
3737
*
3838
* @abstract
3939
*/
40-
class ReactNativeComponent<DefaultProps, Props, State>
41-
extends React.Component<Props, State> {
40+
class ReactNativeComponent<DefaultProps, Props, State> extends React.Component<
41+
Props,
42+
State,
43+
> {
4244
static defaultProps: DefaultProps;
4345
props: Props;
4446
state: State;

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
119119
case HostRoot: {
120120
const updateQueue = finishedWork.updateQueue;
121121
if (updateQueue !== null) {
122-
const instance = finishedWork.child !== null
123-
? finishedWork.child.stateNode
124-
: null;
122+
const instance =
123+
finishedWork.child !== null ? finishedWork.child.stateNode : null;
125124
commitCallbacks(updateQueue, instance);
126125
}
127126
return;
@@ -622,9 +621,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
622621
// For hydration we reuse the update path but we treat the oldProps
623622
// as the newProps. The updatePayload will contain the real change in
624623
// this case.
625-
const oldText: string = current !== null
626-
? current.memoizedProps
627-
: newText;
624+
const oldText: string =
625+
current !== null ? current.memoizedProps : newText;
628626
commitTextUpdate(textInstance, oldText, newText);
629627
return;
630628
}

packages/react-reconciler/src/ReactFiberHydrationContext.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,16 +188,26 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
188188
}
189189
}
190190

191-
function canHydrate(fiber, nextInstance) {
191+
function tryHydrate(fiber, nextInstance) {
192192
switch (fiber.tag) {
193193
case HostComponent: {
194194
const type = fiber.type;
195195
const props = fiber.pendingProps;
196-
return canHydrateInstance(nextInstance, type, props);
196+
const instance = canHydrateInstance(nextInstance, type, props);
197+
if (instance !== null) {
198+
fiber.stateNode = (instance: I);
199+
return true;
200+
}
201+
return false;
197202
}
198203
case HostText: {
199204
const text = fiber.pendingProps;
200-
return canHydrateTextInstance(nextInstance, text);
205+
const textInstance = canHydrateTextInstance(nextInstance, text);
206+
if (textInstance !== null) {
207+
fiber.stateNode = (textInstance: TI);
208+
return true;
209+
}
210+
return false;
201211
}
202212
default:
203213
return false;
@@ -216,12 +226,12 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
216226
hydrationParentFiber = fiber;
217227
return;
218228
}
219-
if (!canHydrate(fiber, nextInstance)) {
229+
if (!tryHydrate(fiber, nextInstance)) {
220230
// If we can't hydrate this instance let's try the next one.
221231
// We use this as a heuristic. It's based on intuition and not data so it
222232
// might be flawed or unnecessary.
223233
nextInstance = getNextHydratableSibling(nextInstance);
224-
if (!nextInstance || !canHydrate(fiber, nextInstance)) {
234+
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
225235
// Nothing to hydrate. Make it an insertion.
226236
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
227237
isHydrating = false;
@@ -237,7 +247,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
237247
nextHydratableInstance,
238248
);
239249
}
240-
fiber.stateNode = nextInstance;
241250
hydrationParentFiber = fiber;
242251
nextHydratableInstance = getFirstHydratableChild(nextInstance);
243252
}

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ type PersistentUpdatesHostConfig<T, P, I, TI, C, CC, PL> = {
152152

153153
type HydrationHostConfig<T, P, I, TI, HI, C, CX, PL> = {
154154
// Optional hydration
155-
canHydrateInstance(instance: HI, type: T, props: P): boolean,
156-
canHydrateTextInstance(instance: HI, text: string): boolean,
155+
canHydrateInstance(instance: HI, type: T, props: P): null | I,
156+
canHydrateTextInstance(instance: HI, text: string): null | TI,
157157
getNextHydratableSibling(instance: I | TI | HI): null | HI,
158158
getFirstHydratableChild(parentInstance: I | C): null | HI,
159159
hydrateInstance(

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import type {FiberRoot} from './ReactFiberRoot';
1313
import type {HydrationContext} from './ReactFiberHydrationContext';
1414
import type {ExpirationTime} from './ReactFiberExpirationTime';
1515

16-
import {
17-
getStackAddendumByWorkInProgressFiber,
18-
} from 'shared/ReactFiberComponentTreeHook';
16+
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
1917
import ReactErrorUtils from 'shared/ReactErrorUtils';
2018
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
2119
import {

0 commit comments

Comments
 (0)