TypeScript - can a generic constraint provide “allowed” types?

送分小仙女□ 提交于 2019-12-04 00:46:33

问题


Given the following code...

type Indexable<TKey, TValue> = { [index: TKey]: TValue }

This produces the following error:

An index signature parameter type must be 'string' or 'number'.

Is there a way to constrain TKey to be 'string' or 'number'?


回答1:


As @TitianCernicova-Dragomir indicates, you can't use TKey as the type in an index signature, even if it is equivalent to string or number.

If you know that TKey is exactly string or number, you can just use it directly and not specify TKey in your type:

type StringIndexable<TValue> = { [index: string]: TValue }
type NumberIndexable<TValue> = { [index: number]: TValue }

Aside: It turns out that in a bunch of places in TypeScript, the index type for properties is required to be a string, and not number. In such places, number is usually treated as a kind of subtype of string. That's because in JavaScript, indices are converted to string anyway when you use them, leading to this kind of behavior:

const a = { 0: "hello" };
console.log(a[0]); // outputs "hello"
console.log(a['0']) // *still* outputs "hello"

Since you can't use number in what follows, I will ignore it; if you need to use numeric keys TypeScript will probably let you, or you can convert to string manually. Back to the rest of the answer:


If you want to allow TKey to be more specific than string, meaning only certain keys are allowed, you can use mapped types:

type Indexable<TKey extends string, TValue> = { [K in TKey]: TValue }

You'd use it by passing in a string literal or union of string literals for TKey:

type NumNames = 'zero' | 'one' | 'two';
const nums: Indexable<NumNames, number> = { zero: 0, one: 1, two: 2 };

type NumNumerals = '0' | '1' | '2';
const numerals: Indexable<NumNumerals, number> = {0: 0, 1: 1, 2: 2};

And if you don't want to limit the key to particular literals or unions of literals, you can still use string as TKey:

const anyNums: Indexable<string, number> = { uno: 1, zwei: 2, trois: 3 };

In fact, this definition for Indexable<TKey, TValue> is so useful, it already exists in the TypeScript standard library as Record<K,T>:

type NumNames = 'zero' | 'one' | 'two';
const nums: Record<NumNames, number> = { zero: 0, one: 1, two: 2 };

I therefore recommend you use Record<K,T> for these purposes, since it is standard and other TypeScript developers who read your code are more likely to be familiar with it.


Hope that helps; good luck!




回答2:


You can constrain TKey to be derived from string or number (using extends) but that will not satisfy the compiler. index must be either number or string, not a generic type or any other type for that matter. This is documented in the language spec



来源:https://stackoverflow.com/questions/46885489/typescript-can-a-generic-constraint-provide-allowed-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!