Issue
In this code, I am expecting to be able to assert that __typename
is "Batch":
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = {
source: T
items: T extends "Batch" ? { __typename: "Batch" }[] : { __typename: "Shipment" }[]
}
const state: State = {
source: "Batch",
items: []
}
if (state.source === "Batch") {
state.items.map((i) => {
const typename: "Batch" = i.__typename // error
})
}
However narrowing on state.source
does nothing to narrow the type of state
. It remains as State<"Batch" | "Shipment">
instead of State<"Batch">
. Playground
I found that refactoring the type to a top-level union fixes the problem:
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = T extends "Batch" ? {
source: T
items: { __typename: "Batch" }[]
} : {
source: T
items: { __typename: "Shipment" } []
}
but why is this?
Solution
Simple answer: TypeScript is just not that powerful. Your logic is correct, you can determine the type of items
based on the type of source
, but TypeScript just does not go the extra mile to do that. Perhaps they will make it smarter in some future version, we'll see, but it's not really needed to be honest.
From the point of view of TypeScript, source
and items
are differently typed attributes and are treated separately. Imagine it like so:
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = {
source: T
items: U
}
That's why it does not infer one based on the other.
Your second type works, because the early condition makes it clear that for your whole type the value of T
is one specific value - either "Batch" or "Shipment".
EXTRA
In your situation I would recommend typing it like this
export type State<T extends "Batch" | "Shipment" = "Batch" | "Shipment"> = T extends (infer V) ? {
source: V
items: { __typename: V }[]
} : never;
This way you'll be able to add additional values to your union type (e.g. "Batch" | "Shipment" | "Product"
) and it will still work as intended.
Answered By - KnightsWhoSayNi
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.