If one wants to add flow annotation to object type and reuse some of the types, that was annotated in another object type, it can be done like:
type SharedProperties = { | |
length: number, | |
name: string, | |
}; | |
type NewProperties = { | |
age: number, | |
...SharedProperties, | |
}; |
However, in this case, not all will run smoothly here, as if to check age
will accept, not only number
type as valid but other as well.
If to annotate objects, as it is done above the age
type will become mixed
. This has happened because SharedProperties
has been annotated as an inexact object, which means, that if it doesn’t explicitly have age
property type, it still can have this property, but is not defined in the annotation of the object:
There are a few ways how to annotate NewProperties
to obtain the expected type (means only age
that has number
type would be accepted).
1) Spread SharedProperties
type first and then add the annotation to age
. In this case, even though age
was defined as mixed
(no explicitly) in the SharedProperties
, its type will be redefined by further age
annotation.
This will work, but it will be not resistant for the annotation changes as if for some reason someone will in the future change the order of destructuring SharedProperties
and other properties definition, an error will appear one more time, and it will be an error that is not easy to track.
One more downside of this approach, that is not easy to spot, is that spreading inexact type will make all its parameter be optional, what is not obvious if one doesn’t know that:
2) Add all shared properties annotation in NewProperties
one by one using $PropertyType
.
Con of this approach is the case when SharedProperties
contains a lot of properties. Then manually adding every prop in the NewProperties
definition will diminish most of the pros that we have from having properties types defined in the SharedProperties
in the first place. However, if we would want to obtain annotation only from some props from SharedProperties
(not all) this method will allow us to do this. Note also, that in this case NewProperties
has both name
and length
properties as required ones.
3) Change SharedProperties
to be Exact type. Semantically there are two ways how to make Object annotation to be the exact type (there is no difference between them).
type SharedPropertiesExact1 = {| | |
length: number, | |
name: string, | |
|}; | |
type SharedPropertiesExact2 = $Exact<{ | |
length: number, | |
name: string, | |
}>; |
Then our case will look like:
From my perspective, this solution has the most benefits, as it keeps all the pros that destructuring of the SharedProperties
is bringing in the first places, at the same time it is more resistant to the order in which destructuring and other properties are added in the NewProperties
annotation. Similarly to the previous approach, in this case, both name
and length
properties as mandatory ones.
I have created the eslint rule, that helps to enforce the third option across the project (check for the rule spread-exact-type
in the eslint-plugin-flowtype
set of rules – https://github.com/gajus/eslint-plugin-flowtype#eslint-plugin-flowtype-rules-spread-exact-type). Unfortunately, there is no easy way to detect is the original type exact at the moment when it is created, but this rule allows to check, that $Exact
utility type is added whenever spreading occurs.
Recently my general logic with flow types is that everything should be annotated as exact as possible, if something is annotated not exact, this should be done with the reason under it. Flow maintainers themselves are identifying that migration for Object
type to be exact by default is in their roadmap (check this post for more details https://medium.com/flow-type/on-the-roadmap-exact-objects-by-default-16b72933c5cf).
If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.
1 Comment