-
Notifications
You must be signed in to change notification settings - Fork 425
Refactor of ReactElement React Props equivalence logic #2138
Conversation
src/react/ReactElementSet.js
Outdated
constructor(realm: Realm, equivalenceSet: HashSet<AbstractValue>) { | ||
this.realm = realm; | ||
this.equivalenceSet = equivalenceSet; | ||
export class ReactElementSet extends ReactSet { |
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.
Does inheritance really help here?
I know we use it for the value taxonomy but I'd prefer to avoid it for the rest of the logic. I imagine you could instantiate ReactSet
instead, keep it as a field, and delegate to it when necessary.
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.
What's wrong with inheritance? I find this code cleaner and easier to understand/read now.
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.
Same reason we don't use it in components? :-)
Inheritance makes the dependencies between parts very implicit. It's hard for me to tell what is the contract between ReactSet
and its subclasses. Do they call its methods? Does it call their methods? Does both happen? Since all interaction happens through this.something()
I have to check every method name to verify where it's defined.
This problem usually becomes even more difficult with time, as superclass become coupled to subclasses, and then one of them needs to behave differently but can't "break out".
Using delegation would make it clearer what ReactSet
is doing, and how each of the specific "sets" uses this functionality.
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 have to disagree here, This isn't the same as UI components, this is business logic. I actually find inheritance is fine in cases like this when there's only a single level of inheritance and the contract is explicit.
I'll refactor the classes again though as I don't want to block this PR on arguments of the validity of inheritance :D
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's not blocking, I'm just finding it harder to understand what the code is doing because of the implicitness.
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've redone it all and removed the classes, it's all functions and argument passing now. Functional FTW
src/react/ReactPropsSet.js
Outdated
|
||
if (temporalAlias !== undefined) { | ||
currentMap = this._getKey("temporalAlias", currentMap, visitedValues); | ||
result = this._getValue(temporalAlias, currentMap, visitedValues); |
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.
What case does this handle?
I think this would break if there was a prop called temporalAlias
(very unlikely but should be easy to fix).
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's for snapshotting, I've addressed in latest commit :)
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.
Can we use a symbol or an object as a key in this case? String clashes with user supplied props (which also use strings).
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.
Overall LGTM.
I found it a bit difficult to understand what methods on ReactSet
are "internal" to it and which are meant to be called by subclasses. Would prefer delegation but don't care strongly.
Found a few bugs (#2139) while testing this but they're on master too.
Btw I didn't mean to drop classes :-) I meant that instead of having class ReactPropsSet {
reactSet = new ReactSet(whatItActuallyNeeds);
add() {
var something = this.reactSet.doSomething();
// ...
} Then interaction between them is explicit and I don't need to guess which methods are private to the base class vs which are meant to be accessible by superclass, and whether base class ever calls the superclass. Anyway, no need to rewrite—hope this explains what I meant. |
Let's just revert the last commit? The argument count in functions is wild. I agree classes are better in this case. I'm sorry if my last comments were confusing. I was strictly speaking about dropping inheritance, not dropping classes. |
Anyway. Whatever you think is clearer 👍 Let's not get stuck on this. |
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.
Looks good to me.
src/react/utils.js
Outdated
@@ -91,6 +91,20 @@ export function isReactElement(val: Value): boolean { | |||
return false; | |||
} | |||
|
|||
export function isReactProps(val: Value): boolean { |
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.
Perhaps call this "inReactProps"? Come to think of it, reactProps is not a very helpful name for someone unfamiliar with the code base.
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.
Maybe isReactPropsObject
? A single object is called "props", it's not a plural in this sense.
The fact that it checks a set is an implementation detail. It's meant to be more of a brand check conceptually. Like isArray
rather than inArrays
or isArrays
.
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.
@hermanventer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
src/react/utils.js
Outdated
@@ -1153,7 +1153,18 @@ export function hardModifyReactObjectPropertyBinding( | |||
"hardModifyReactObjectPropertyBinding can only be used on final objects!" | |||
); | |||
let binding = object.properties.get(propName); | |||
invariant(binding !== undefined); | |||
if (binding === undefined) { | |||
binding = { |
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 looks like we're trying to create an object to match the shape below. Can we instead make peace with it being undefined
, and remove the invariant below? Object.assign
should work either way.
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.
If it's undefined, then we need to create the binding. That's why we do it here.
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.
@trueadm is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Summary: Release notes: none This PR contains some fixes and small tidy ups that would have been in #2138. There was a bug to do with the React props equivalence system and snapshots that was found when #2137 was merged (along with the fix I commented to on that PR). This PR unblocks #2137 to land as it should now pass our internal bundle test. Closes #2146 Differential Revision: D8556958 Pulled By: trueadm fbshipit-source-id: dd774de76059e00af1dff305dd3618c3bfed06ea
Release notes: none
After studying the issues in #2137, I understood that we currently make some assumptions in terms of the ReactElement equivalence system that are incompatible with what we want from Prepack going forward. This PR aims at fixing those assumptions so they fall more into line with the changes #2137.
This PR makes the following changes:
hardModifyReactObjectPropertyBinding
to modify final objects that React controls rather thanProperties.Set
. This function bypasses the normal route of putting the property changes on the effects – thus making it so the property changes are permanent and cannot be reverted. Given the object is immutable, there should never be a case where the property may change anyway – we only change the property as part of the equivalence phase and during reconciliation where the React internals are at play. This is only safe for objects explicitly created by React and marked as final by React – no other object can go through this codepath. Any attempt to use this dangerous function on a non-final object will result in an invariant.makeNotFinal
and usehardModifyReactObjectPropertyBinding
instead.