Skip to content

Commit acabf11

Browse files
authored
Update Flow and Fix Hydration Types (#11493)
* Update Flow * Fix createElement() issue The * type was too ambiguous. It's always a string so what's the point? Suppression for missing Flow support for {is: ''} web component argument to createElement() didn't work for some reason. I don't understand what the regex is testing for anyway (a task number?) so I just removed that, and suppression got fixed. * Remove deleted $Abstract<> feature * Expand the unsound isAsync check Flow now errors earlier because it can't find .type on a portal. * Add an unsafe cast for the null State in UpdateQueue * Introduce "hydratable instance" type The Flow error here highlighted a quirk in our typing of hydration. React only really knows about a subset of all possible nodes that can exist in a hydrated tree. Currently we assume that the host renderer filters them out to be either Instance or TextInstance. We also assume that those are different things which they might not be. E.g. it could be fine for a renderer to render "text" as the same type as one of the instances, with some default props. We don't really know what it will be narrowed down to until we call canHydrateInstance or canHydrateTextInstance. That's when the type is truly refined. So to solve this I use a different type for hydratable instance that is used in that temporary stage between us reading it from the DOM and until it gets refined by canHydrate(Text)Instance. * 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 13c491a commit acabf11

14 files changed

+65
-51
lines changed

.flowconfig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ suppress_type=$FlowFixMe
3636
suppress_type=$FixMe
3737
suppress_type=$FlowExpectedError
3838

39-
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)
40-
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)?:? #[0-9]+
39+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
40+
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
4141
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
4242
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
4343

4444
[version]
45-
^0.53.1
45+
^0.57.3

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"fbjs": "^0.8.16",
5858
"fbjs-scripts": "^0.6.0",
5959
"filesize": "^3.5.6",
60-
"flow-bin": "^0.53.1",
60+
"flow-bin": "^0.57.3",
6161
"git-branch": "^0.3.0",
6262
"glob": "^6.0.4",
6363
"glob-stream": "^6.1.0",

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ function updateDOMProperties(
351351
}
352352

353353
export function createElement(
354-
type: *,
354+
type: string,
355355
props: Object,
356356
rootContainerElement: Element | Document,
357357
parentNamespace: string,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ReactNativeComponent<DefaultProps, Props, State> extends React.Component<
4141
Props,
4242
State,
4343
> {
44-
static defaultProps: $Abstract<DefaultProps>;
44+
static defaultProps: DefaultProps;
4545
props: Props;
4646
state: State;
4747

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ if (__DEV__) {
6464
var warnedAboutStatelessRefs = {};
6565
}
6666

67-
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
68-
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
67+
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
68+
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
6969
hostContext: HostContext<C, CX>,
7070
hydrationContext: HydrationContext<C, CX>,
7171
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
3333

3434
var {invokeGuardedCallback, hasCaughtError, clearCaughtError} = ReactErrorUtils;
3535

36-
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
37-
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
36+
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
37+
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
3838
captureError: (failedFiber: Fiber, error: mixed) => Fiber | null,
3939
) {
4040
const {getPublicInstance, mutation, persistence} = config;

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ import {
4343
} from './ReactFiberContext';
4444
import {Never} from './ReactFiberExpirationTime';
4545

46-
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
47-
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
46+
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
47+
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
4848
hostContext: HostContext<C, CX>,
4949
hydrationContext: HydrationContext<C, CX>,
5050
) {

packages/react-reconciler/src/ReactFiberHostContext.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export type HostContext<C, CX> = {
2828
resetHostContainer(): void,
2929
};
3030

31-
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
32-
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
31+
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
32+
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
3333
): HostContext<C, CX> {
3434
const {getChildHostContext, getRootHostContext} = config;
3535

packages/react-reconciler/src/ReactFiberHydrationContext.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export type HydrationContext<C, CX> = {
2929
popHydrationState(fiber: Fiber): boolean,
3030
};
3131

32-
export default function<T, P, I, TI, PI, C, CC, CX, PL>(
33-
config: HostConfig<T, P, I, TI, PI, C, CC, CX, PL>,
32+
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
33+
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
3434
): HydrationContext<C, CX> {
3535
const {shouldSetTextContent, hydration} = config;
3636

@@ -82,7 +82,7 @@ export default function<T, P, I, TI, PI, C, CC, CX, PL>(
8282
// The deepest Fiber on the stack involved in a hydration context.
8383
// This may have been an insertion or a hydration.
8484
let hydrationParentFiber: null | Fiber = null;
85-
let nextHydratableInstance: null | I | TI = null;
85+
let nextHydratableInstance: null | HI = null;
8686
let isHydrating: boolean = false;
8787

8888
function enterHydrationState(fiber: Fiber) {
@@ -188,16 +188,26 @@ export default function<T, P, I, TI, 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, 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, PI, C, CC, CX, PL>(
237247
nextHydratableInstance,
238248
);
239249
}
240-
fiber.stateNode = nextInstance;
241250
hydrationParentFiber = fiber;
242251
nextHydratableInstance = getFirstHydratableChild(nextInstance);
243252
}

0 commit comments

Comments
 (0)