-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Design Meeting Notes, 12/16/2016 #12997
Comments
12/16/2016 ? |
You don't 0-index your months? 😉 |
Can this reasoning be elaborated on? Can it be re-considered at this point? What does "exact types" mean in this case? How does this answer "is there any reason not to do it"? Forgive me if I'm being annoying for bringing this issue up in more than one thread, but it's still a big problem for me (and I see even @sandersn thought it looked like an oversight, which I was quite convinced of myself when I ran into it!) I'll elaborate on my use case, which is straight from the Redux documentation on using object spread. Say I have an object like this:
With React/Flux/Redux and other immutable object updating code we want to update this object by replacing it completely, copying over unchanged keys and overwriting specific keys. A very common pattern to do this in JS is to use
I dreamed for a long time of using object spread in TypeScript to do this (as my Babel colleagues have been using this approach for awhile), only to find out its design is not very safe and I probably won't get to use it much. For example, if I use the above code (which does work) and then refactor my interface:
I should have errors all over my code (and I will have errors all over my app at runtime!), but I have none, because
But this is a much worse pattern and more error prone, I don't think I need to explain why. Besides, it's not idiomatic JS -- using object spread is (or Object.assign). Even if we take the refactoring story away, simply writing code with the current object spread design is susceptible to typos, causing unexpected runtime behavior without compile warnings:
In general, I just don't see how this can be considered an error:
But this is not:
Maybe the explanation is there, but I don't get it. |
@aaronbeall exact types refers to the idea of an object type that strictly rejects any type with more properties than have been declared. For example: class Dog {
name: string;
age: number;
}
interface Nameable {
name: string;
}
declare let d: Dog;
declare function callOver(thingWithName: Nameable): void;
callOver(d); If we had the concept of exact types in TS, and In this design meeting, we were conservative - we decided not to add the excess property checking until we understood what our feelings were around exact types. We can always reconsider this again, but I don't think we're significantly closer on exact types for what it's worth. |
Thanks for the explanation of exact types, @DanielRosenwasser. I guess my confusion comes from the fact that excess property checks seem to already exist for object literals:
But if it contains a spread the same part of the expression that was checked is no longer checked.
I really thought this was an oversight, not an intentional design. I take it there is something fundamentally different about an object literal expression that contains a spread somewhere in it, but it doesn't really make sense to me. |
One thing is that because the expression being spreaded might be of a subtype of its declared type, you're already potentially including in "extra" keys. So in that sense, we know we can't know if there are extra properties or not, and you might need to "override" one of those keys to have some different value for whatever reason. |
You mean you wouldn't be able to do this?
My first thought is that's exactly what I'd expect. If you want to throw a bunch of random "stuff" onto a type and override some of those things "for whatever reason", I would think I'm using the wrong type to begin with. In my own use case I can't think of reason I'd do that. But I see how that's a less "conservative" approach. I imagine its overly complex to just include the spread props as known props, but otherwise check for unknown props? I honestly wouldn't care either way, it doesn't matter to my use case. The thing I'm hung up on is explicitly trying to assign an unknown prop (regardless of what else is in the literal). |
We're trying to migrate to TS with some of our react / redux projects which makes heavy use of object spread and have come across the issue. I understand the points of @RyanCavanaugh but as a layman looking at this @aaronbeall has explicitly typed the const 'o' in the example above. Regardless of the properties / typing of 'stuff' 'o' cannot / should not have the property 'b'? Is there anyway I can benefit from typing in this scenario in order to prevent invalid properties? Thanks, |
I could not follow all the justifications made in the notes and comments, but what I still see inconsistency. let a = { x: 1, y: 2 }
let b = { t: 1, r: 2 }
type AandB = typeof a & typeof b
// can add extra props with stpred
let c: AandB = {
...a, ...b,
extra: 1 // no error
}
// can not overide props with sperad
let cOverrideX: AandB = { //error x incompatilble
...a, ...b,
x: '1'
}
// can add extran props and override with Object.assgin/merge
let cAssgin: AandB =
Object.assign(
a, b,
{ x: 'x', extra: 1 }, // no error, can be extra props, override props types
)
// plain explicit version
let cPlain: AandB = {
x: a.x,
y: a.y,
t: b.t,
r: b.r,
extra: 1 // error
} So do you propose if one need strict type checking on object structure and props that one should be super explicit and do not use spread or assgin/merge methods but just go verbose and straightforward? Could some one bring a real case examples that show benifits of current spread/assgin/merge behaviour? |
Another person trying to convert Redux code to TypeScript, spreads are all over the reducers. Not having type information is definitely unfortunate here, since it's one of the key areas where type hints are beneficial. |
Please upvote #12936, the exact types issue, since that should be the correct fix for this problem. (It only has 6 upvotes right now!) |
Intrinsic
object
Type (#12501)Can't be an object-type (in terms of how the spec/compiler defines object types).
This is more like a primitive.
Object
.{}
(and almost anything is assignable toObject
).object
should be assignable to{}
andObject
.Function
?number
->Number
->object
).Covers a majority of the use-cases.
object & { a?, b?, c? }
.type ObjectOf<T> = object & t
.{ a?, b?, c? }
they truly do mean they only want an object-type.Conclusion:
object
, still need to think about exact types.Excess Property Checks on Union types (#12745)
Is a pretty complex problem.
#12904
Options:
Union type property checks
Weak types
Exact types
If we did exact types
If we did weak type detection
Freshness Maintenance for Object Spread (#12717)
Conclusion: No.
(Reason: freshness checking was to catch issues for things like options bags - this sounds more like a potential case for exact types)
The text was updated successfully, but these errors were encountered: