Why does the following assertion work:
interface AllRequired {
a: string;
b: string;
}
let all = {a: "foo"} as AllRequired; // No error
But this assertion gives an error:
interface SomeOptional {
a?: string;
b: string;
}
let some = {a: "foo"} as SomeOptional; // Error: Property 'b' missing
The only difference I can see is making one of the interface properties optional (?). It seems that if all properties are not optional, I can assert a partial object to the interface, but as soon as any of the interface properties are optional, I cannot assert a partial object anymore. This doesn't really make sense to me and I've been unable to find an explanation of this behavior. What's going on here?
For context: I encountered this behavior while trying to work around the problem that React's setState() takes a partial state object, but TypeScript doesn't yet have partial types to make this work properly with your state interface. As a workaround I came up with setState({a: "a"} as MyState) and found this works as long as interface MyState fields are all non-optional, but fails as soon as some properties are optional. (Making all properties optional is a workaround, but very undesirable in my case. )
Type assertions can only be used to convert between a type and a subtype of it.
Let's say you declared the following variables:
declare var foo: number | string;
declare var bar: number;
Note number is a subtype of number | string, meaning any value that matches the type number (e.g. 3) also matches number | string. Therefore, it is allowed to use type assertions to convert between these types:
bar = foo as number; /* convert to subtype */
foo = bar as number | string; /* convert to supertype (assertion not necessary but allowed) */
Similarly, { a: string, b: string } is a subtype of { a: string }. Any value that matches { a: string, b: string } (e.g. { a: "A", b: "B" }) also matches { a: string }, because it has an a property of type string.
In contrast, neither of { a?: string, b: string } or { a: string } is a subtype of the other.
Some values (e.g. { b: "B" }) match only the former, and others (e.g. { a: "A" }) match only the latter.
If you really need to convert between unrelated types, you can work around this by using a common supertype (such as any) as an intermediate:
let foo = ({ a: "A" } as any) as { a?: string, b: string };
The relevant part in the spec is 4.16 Type Assertions:
In a type assertion expression of the form < T > e, e is contextually typed (section 4.23) by T and the resulting type of e is required to be assignable to T, or T is required to be assignable to the widened form of the resulting type of e, or otherwise a compile-time error occurs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With