Reputation: 1829
The function below is able to understand that the key "banana" cannot have the value "red":
type Fruits = {
banana: 'yellow' | 'green'
strawberry: 'red'
}
const fruit = <K extends keyof Fruits>(module: K, action: Fruits[K]) => true
fruit('banana', 'red') // error: not assignable to parameter of type '"yellow" | "green"
Is there any way to make something similar with tuples?
type FruitTuple<K extends keyof Fruits> = [K, Fruits[K]]
const c: FruitTuple<keyof Fruits> = ['banana', 'red'] // no error
Or even with template literals?
type FruitTemplate<K extends keyof Fruits> = `${K}.${Fruits[K]}`
const c: FruitTemplate<keyof Fruits> = 'banana.red' // no error
Or maybe objects?
type Fruit<K extends keyof Fruits> = {
name: K
color: Fruits[K]
}
const d: Fruit<keyof Fruits> = { name: 'banana', color: 'red' } // no error
Upvotes: 0
Views: 68
Reputation: 11581
Yes! But as pointed out in the comment, you can't pass keyof Fruits
into the generic, because that will not provide enough detail for TypeScript to infer which fruit you have specified. If you explicitly pass in the literal type into each of your examples, it works as expected.
type FruitTuple<K extends keyof Fruits> = [K, Fruits[K]]
const c: FruitTuple<'banana'> = ['banana', 'red'] // error
type FruitTemplate<K extends keyof Fruits> = `${K}.${Fruits[K]}`
const d: FruitTemplate<'banana'> = 'banana.red' // error
type Fruit<K extends keyof Fruits> = {
name: K
color: Fruits[K]
}
const e: Fruit<'banana'> = { name: 'banana', color: 'red' } // error
This highlights the power of using generics with functions: TypeScript can infer the actual type used when the function is invoked. However when instantiating values directly with generic tuples, template literals, objects etc. you don't have the same power to infer the type, hence the "redundancy" of typing something like const c: FruitTuple<'banana'> = ['banana', 'red']
However that doesn't mean you can't still use your custom tuple types or whatever else. But might help you out to make some helper functions to more easily instantiate the values and types you want.
function makeFruitTuple<K extends keyof Fruits>(fruit: K, color: Fruits[K]): FruitTuple<K> {
return [fruit, color];
}
const myFruitTuple1 = makeFruitTuple('banana', 'yellow'); // ok
const myFruitTuple2 = makeFruitTuple('banana', 'red'); // error
Upvotes: 1