Typescript: När är en typ spridbar?
Jag har problem med att använda objektspreading i Typescript-funktioner med Type-variabler.
Är det överhuvudtaget möjligt (just nu)? Om inte, vilka är kortfattade alternativ?
Här är vad jag observerade med både Typescript v2.6 och v2.7-dev:
I funktionsdefinitionerna nedan kompilerar de ok utan problem men de err ger följande kompileringsfel:
TS2698: Spridningstyper kan endast skapas från objekttyper.
interface IMessages {
[msgKey: string]: string;
}
const ok1 = () => {
type TFieldNames = "a" | "b" | "c";
const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
const fieldName = "XXX" as TFieldNames;
const otherMessages = fieldErrors[fieldName]; // has type IMessages
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
const ok2 = () => {
type TFieldNames = keyof { a: number; b: number; c: number };
const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
const fieldName = "XXX" as TFieldNames;
const otherMessages = fieldErrors[fieldName]; // has type IMessages
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
const ok3 = () => {
type TFieldNames = string;
const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
const fieldName = "XXX" as TFieldNames;
const otherMessages = fieldErrors[fieldName]; // has type "any"
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
const err1 = <TFieldNames extends string>() => {
const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
const fieldName = "XXX" as TFieldNames;
const otherMessages = fieldErrors[fieldName]; // has type "any"
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
const err2 = <TFields extends { [key: string]: any }>() => {
const fieldErrors: { [field in keyof TFields]?: IMessages } = {};
const fieldName = "XXX" as keyof TFields;
const otherMessages = fieldErrors[fieldName]; // has type "any"
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
const err3 = <TFields extends object>() => {
const fieldErrors: { [field in keyof TFields]?: IMessages } = {};
const fieldName = "XXX" as keyof TFields;
const otherMessages = fieldErrors[fieldName]; // has type "any"
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
Svar #1
Om du tittar på den härledda typen i TypeScript Playground för otherMessages i err1, är det
const otherMessages: { [field in TFieldNames]?: IMessages; }[TFieldNames]
Och det börjar fungera om du lägger till en explicit typ för otherMessages:
const err1b = <TFieldNames extends string>() => {
const fieldErrors: { [field in TFieldNames]?: IMessages } = {};
const fieldName = "XXX" as TFieldNames;
const otherMessages: IMessages | undefined = fieldErrors[fieldName];
fieldErrors[fieldName] = { ...otherMessages, too_big: "is too big" };
};
Det verkar som att TypeScript på egen hand inte kan förenkla { [field in TFieldNames]?: IMessages; }[TFieldNames] till IMessages | undefined. Ser ut som en bugg.
Svar #2
För att komplettera Artems svar: detta gränssnitt är inte giltigt i Playground :
interface MessagesMap<TFieldNames extends string> {
[field in TFieldNames]?: IMessages; // TsError: A computed property name must be of type 'string', 'number', 'symbol' or 'any'
}
Så, err1-funktionen borde inte kompileras från början.
Om du behöver ett objekt med strängindexerade egenskaper uppmuntrar denna TypeScript-begränsning oss att förenkla designen och att föredra att använda en enkel string-typ istället för en mer precis typ som använder generiska och keyof- eller in-operatörer:
interface FieldMessages {
[field: string]: IMessages;
}
Eller använd ett Map<K extends string, IMessages>-objekt.
