If I have an enum and want to use the enum values as key in some objects, I can declare the types like so:
enum Foo {
X, Y
}
let map: FooMapType = {
[Foo.X]: "abc",
[Foo.Y]: "def"
}
type FooMapType = {
[key in Foo]: string
}
This works perfectly fine. However, if I try to do the same thing using interface:
interface FooMapInterface {
[key in Foo]: string
}
I get these errors:
TS1169: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
TS2304: Cannot find name 'key'.
Why is there a difference between interface vs type in this case? What is the restriction on interface, if any, that resulted in this design decision?
CodePudding user response:
You're not allowed to use mapped type keys (of the form [P in K]: T
where K
is a key-like type) as keys of an interface
. Right now you get some confusing error messages because the compiler misinterprets what you are doing as using a computed property declaration (of the form [k]: U
where k
is a key-like value, not a type). There is an open issue at microsoft/TypeScript#18299 asking for some fix for these error messages.
I don't know if there's any canonical documentation that says exactly why you cannot use mapped types here. In addition to normal properties whose keys are statically-known string or numeric literal types, interfaces are allowed to have call signatures, construct signatures, method signatures, and index signatures, all of which are also allowed inside class
instance types. Mapped types are not in the list of accepted things.
If you want to make an interface
out of your mapped type, you can do it by extending the mapped type as a new interface. You are allowed to extend a named type with statically known keys. And since the keys of FooMapType
are statically known (as opposed to some unspecified generic type), you can write this:
interface FooMapInterface extends FooMapType { } // okay
And see that it works:
declare const fooMapInterface: FooMapInterface;
fooMapInterface[Foo.X].toUpperCase(); // okay
So you can get this behavior, you just can't write it directly.