-
Notifications
You must be signed in to change notification settings - Fork 47.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flow-ify ReactPartialRenderer #11251
Flow-ify ReactPartialRenderer #11251
Conversation
childIndex: number, | ||
context: Object, | ||
footer: string, | ||
debugElementStack: Array<any>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all the Frame
annotations below only add debugElementStack
in dev mode which causd a lot of errors in the logging utility functions. Just declaring it as always present (which it is in dev
, and I think appropriately defended against in prod
) solved a lot of the flow errors.
d8de171
to
6484d95
Compare
@@ -320,6 +321,7 @@ function validateRenderResult(child, type) { | |||
|
|||
function resolve(child, context) { | |||
while (React.isValidElement(child)) { | |||
child = (child: React.Element); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not really sure why Flow isn’t understanding that child
is a React.Element
at this point without this extra hint. I thought that by typing the callsites to resolve
this would just work.
// Assume all trees start in the HTML namespace (not totally true, but | ||
// this is what we did historically) | ||
domNamespace: Namespaces.html, | ||
children, | ||
childIndex: 0, | ||
context: emptyObject, | ||
footer: '', | ||
}; | ||
}: any); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of these are because of #11251 (comment)
var frame = {
...everythingExcept_debugElementStack
};
if (process.env.NODE_ENV) {
frame.debugElementStack = [];
}
So the frame: Frame = ({}: any)
annotation is so that flow knows that this object can have the debugElementStack
property.
var frame: Frame = ({
...everythingExcept_debugElementStack
// the any is to prevent Flow from considered this a
// sealed object without the debugElementStack
}: any);
without this then the following chunks of code have a lot of errors due to debugElementStack
being undefined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing something as any
is generally as bad as not typing in the first place. Or possibly worse because now you have an illusion of type safety.
Could we perhaps use approach like
react/src/renderers/dom/fiber/ReactDOMFiberEntry.js
Lines 200 to 201 in 90370f2
if (__DEV__) { | |
const parentHostContextDev = ((parentHostContext: any): HostContextDev); |
and
const parentNamespace = ((parentHostContext: any): HostContextProd); |
?
Or just treat that field as always optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is typed similarly, just var binding: Type = ({}: any);
instead of var binding = (({}: any): Type);
Though I’ll look to into make the making separate *Prod
and *Dev
variants yet.
domNamespace: parentNamespace, | ||
children, | ||
childIndex: 0, | ||
context: context, | ||
footer: '', | ||
}; | ||
}: Object); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
domNamespace: getChildNamespace(parentNamespace, element.type), | ||
tag, | ||
children, | ||
childIndex: 0, | ||
context: context, | ||
footer: footer, | ||
}; | ||
}: any); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to get rid of any's.
bacc1e7
to
c30783f
Compare
@@ -319,6 +321,7 @@ function validateRenderResult(child, type) { | |||
|
|||
function resolve(child, context) { | |||
while (React.isValidElement(child)) { | |||
child = (child: React.Element); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cast seemed suspicious to me. Because if it "worked" then Flow would've inferred it anyway.
I checked, and it seems like React.Element
is any
😞 Flow can be so confusing. This is not the thing we're supposed to be using, apparently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, one of my few wishes for flow is that it would be easier to see what it thought something was at any point in time and why.
constructor(element, makeStaticMarkup) { | ||
stack: Array<Frame>; | ||
exhausted: boolean; | ||
currentSelectValue: null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a very helpful annotation. It's not always null
. So by encoding it like this we actually make it harder to fix the Flow coverage because now it looks like fixing this is a Flow violation.
720e328
to
d9bab39
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a few changes.
while (React.isValidElement(child)) { | ||
// Safe because we just checked it's an element. | ||
var element: ReactElement = ((child: any): ReactElement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how to safely "cast" things to elements. (Ideally isValidElement()
should work as a refinement but Flow folks haven't implemented that yet.)
I introduced a separate variable because you can't change the type of the existing one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the : ReactElement
on both sides of the assignment? I’ve only ever done one or the other and it always seemed to work as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary. Can remove.
child: mixed, | ||
context: Object, | ||
): {| | ||
child: mixed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mixed is always better than any.
childIndex: 0, | ||
context: emptyObject, | ||
footer: '', | ||
}; | ||
if (__DEV__) { | ||
topFrame.debugElementStack = []; | ||
((topFrame: any): FrameDev).debugElementStack = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the trick I meant to suggest. Keep both PROD and DEV paths typechecked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying (and unable) to find a way to get Flow to know that the only variant between Frame
and FrameDev
that could ever make it to the *CurrentDebugStack
methods above were FrameDev
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it's possible. I think using PROD type most things and then unsafely casting to DEV type for DEV things is the way to go.
d9bab39
to
12fcd71
Compare
@@ -66,7 +66,8 @@ if (__DEV__) { | |||
var currentDebugStack = null; | |||
var currentDebugElementStack = null; | |||
var setCurrentDebugStack = function(stack) { | |||
currentDebugElementStack = stack[stack.length - 1].debugElementStack; | |||
var frame: Frame = stack[stack.length - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this entire chunk of code is inside a if (__DEV__)
block I think we could safely say that var frame: FrameDev = stack[stack.length - 1];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess yea.
* flow ReactPartialRenderer * prettier * moving flow type around; * Move anys to DEV-only code path and keep it typechecked * Increase Flow coverage
Closes #11175
cc @gaearon