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'?
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!
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