Hur kan jag få TypeScript att ge ett fel för saknad mappad nyckel?
Det här är vad jag har:
type TransferType = 'INTERNAL' | 'WITHDRAWAL' | 'DEPOSIT'
type TransferEvents = Record<TransferType, Record<string, TypeFoo | TypeBar>>
export interface EventsTooltip extends TransferEvents {
some: string;
extra: number;
keys: boolean:
INTERNAL: Record<string, TypeFoo>;
WITHDRAWAL: Record<string, TypeBar>;
DEPOSIT: Record<string, TypeBar>;
}
Är det möjligt att få TypeScript att generera ett fel om jag lägger till en annan typ i TransferType, som 'CORRECTION', men glömmer att lägga till den i EventsTooltip? Samtidigt som jag behåller möjligheten att vara mer specifik för värdetypen (TypeFoo eller TypeBar) i posterna?
Svar #1
Jag kan komma på ett par sätt.
Ett sätt är att definiera TransferType i termer av en objekttyp som har dessa Record<string, TypeFoo> osv. värden, på detta sätt:
interface TransferRecords { // Or whatever name makes sense
INTERNAL: Record<string, TypeFoo>;
WITHDRAWAL: Record<string, TypeBar>;
DEPOSIT: Record<string, TypeBar>;
}
type TransferType = keyof TransferRecords; // Type is "INTERNAL" | "WITHDRAWAL" | "DEPOSIT"
export interface EventsTooltip extends TransferRecords {
some: string;
extra: number;
keys: boolean;
};
Lekplatslänk
Med det kan EventsTooltip och TransferType inte hamna ur synk eftersom de båda hämtar information från samma källa (TransferRecords).
Om du inte ville göra det, kan jag inte komma på ett sätt att få typen att misslyckas, men jag kan komma på ett sätt att få användningen av typen att misslyckas, så här:
type TransferType = "INTERNAL" | "WITHDRAWAL" | "DEPOSIT";
type BaseEventsTooltip = {
some: string;
extra: number;
keys: boolean;
};
export type EventsTooltip = BaseEventsTooltip & {
[key in TransferType]: key extends "INTERNAL"
? Record<string, TypeFoo>
: key extends "WITHDRAWAL"
? Record<string, TypeBar>
: key extends "DEPOSIT"
? Record<string, TypeBar>
: never;
};
Det är omständligt och klumpigt, men det betyder att om du har någon kod som denna någonstans;
const tooltip: EventsTooltip = {
some: "x",
extra: 42,
keys: true,
INTERNAL: /*...*/,
WITHDRAWAL: /*...*/,
DEPOSIT: /*...*/,
};
och senare lägger du till till TransferType, kommer tooltip ovan att visa ett fel eftersom den nya nyckeln inte kommer att finnas (och inte kan tillhandahållas, eftersom dess typ skulle vara never). Förhoppningsvis skulle det avslöja att du behöver uppdatera EventsTooltip-typen.
Lekplats med tooltip som fungerar
Lekplats med ett annat element i TransferType som orsakar att tooltip misslyckas
Det är klumpigt och jag hoppas att det finns en bättre lösning, men det fungerar. :-)
Svar #2
Du kan använda en generisk typ som helt enkelt returnerar den generiska efter att ha gjort en kompileringstidkontroll på den generiska:
type TransferType = 'INTERNAL' | 'WITHDRAWAL' | 'DEPOSIT'
type TypeFoo = { __lock1: never };
type TypeBar = { __lock2: never };
type TransferEvents = Record<TransferType, Record<string, TypeFoo | TypeBar>>
type CreateEventsTooltip<
// Compile-time check here.
T extends TransferEvents,
> = T;
export type EventsTooltip = CreateEventsTooltip<{
some: string;
extra: number;
keys: boolean;
INTERNAL: Record<string, TypeFoo>;
WITHDRAWAL: Record<string, TypeBar>;
DEPOSIT: Record<string, TypeBar>;
}>;
export type EventsTooltip2 = CreateEventsTooltip<{
some: string;
extra: number;
keys: boolean;
INTERNAL: Record<string, TypeFoo>;
WITHDRAWAL: Record<string, TypeBar>;
// We're missing "DEPOSIT" so it errors
// DEPOSIT: Record<string, TypeBar>;
}>;
TypeScript Playground-länk
Redigera: Dessutom, om du vill ha tillbaka de där indexsignaturerna med grundare typning från när du utvidgade från när det använde gränssnitt, kan du ändra CreateEventsTooltip-typen så här:
type CreateEventsTooltip<T extends TransferEvents> = T & TransferEvents;
