Bezpieczne warianty
Tworzenie tablicy lub krotki kluczy z poziomu interfejsu z kontrolą bezpieczeństwa w czasie kompilacji wymaga trochę kreatywności. Typy są usuwane w czasie wykonywania, a typy obiektów (nieuporządkowane, nazwane) nie mogą być konwertowane na typy krotek (uporządkowane, bez nazw) bez uciekania się do nieobsługiwanych technik .
Porównanie z innymi odpowiedziami
Wszystkie proponowane warianty uwzględniają / wywołują błąd kompilacji w przypadku zduplikowanych lub brakujących elementów krotki, biorąc pod uwagę typ obiektu odniesienia, taki jak IMyTable. Na przykład zadeklarowanie typu tablicy (keyof IMyTable)[]nie może wychwycić tych błędów.
Ponadto nie wymagają konkretnej biblioteki (ostatnie zastosowania wariantu ts-morph, które rozważałbym jako ogólny wrapper kompilatora), emitują typ krotki w przeciwieństwie do obiektu (tylko pierwsze rozwiązanie tworzy tablicę) lub typ szerokiej tablicy (w porównaniu do te odpowiedzi ) i wreszcie nie potrzebują zajęć .
Wariant 1: prosta tablica typu
function createKeys(keyRecord: Record<keyof IMyTable, any>): (keyof IMyTable)[] {
return Object.keys(keyRecord) as any
}
const keys = createKeys({ isDeleted: 1, createdAt: 1, title: 1, id: 1 })
+najłatwiejszy +-podręcznik z -tablicą autouzupełniania , bez krotki
Plac zabaw
Jeśli nie lubisz tworzyć rekordów, spójrz na tę alternatywę z Settypami asercji i .
Wariant 2: krotka z funkcją pomocniczą
function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
return t
}
++-instrukcja krotki z automatycznym uzupełnianiem, +-bardziej zaawansowane, złożone typy
Plac zabaw
Wyjaśnienie
createKeyswykonuje kontrole w czasie kompilacji, łącząc typ parametru funkcji z dodatkowymi typami asercji, które generują błąd dla nieodpowiednich danych wejściowych. (keyof IMyTable)[] | [keyof IMyTable]jest "czarną magią" sposobem na wymuszenie wnioskowania krotki zamiast tablicy po stronie wywoływanej. Alternatywnie możesz użyć potwierdzenia const /as const po stronie wywołującego.
CheckMissingsprawdza, jeśli Tbrakuje kluczy z U:
type CheckMissing<T extends readonly any[], U extends Record<string, any>> = {
[K in keyof U]: K extends T[number] ? never : K
}[keyof U] extends never ? T : T & "Error: missing keys"
type T1 = CheckMissing<["p1"], {p1:any, p2:any}>
type T2 = CheckMissing<["p1", "p2"], { p1: any, p2: any }>
Uwaga: T & "Error: missing keys"dotyczy tylko ładnych błędów IDE. Możesz też pisać never. CheckDuplicatessprawdza elementy z podwójną krotką:
type CheckDuplicate<T extends readonly any[]> = {
[P1 in keyof T]: "_flag_" extends
{ [P2 in keyof T]: P2 extends P1 ? never :
T[P2] extends T[P1] ? "_flag_" : never }[keyof T] ?
[T[P1], "Error: duplicate"] : T[P1]
}
type T3 = CheckDuplicate<[1, 2, 3]>
type T4 = CheckDuplicate<[1, 2, 1]>
Uwaga: Więcej informacji na temat sprawdzania unikalnych przedmiotów w krotkach znajduje się w tym poście . Dzięki TS 4.1 możemy również nazwać brakujące klucze w ciągu błędu - spójrz na ten Playground .
Wariant 3: typ rekurencyjny
W wersji 4.1 TypeScript oficjalnie obsługuje warunkowe typy rekurencyjne , które mogą być tutaj również potencjalnie użyte. Chociaż obliczenia typu są kosztowne ze względu na złożoność kombinacyjną - wydajność znacznie spada dla więcej niż 5-6 pozycji. Podaję tę alternatywę dla kompletności ( Playground ):
type Prepend<T, U extends any[]> = [T, ...U]
type Keys<T extends Record<string, any>> = Keys_<T, []>
type Keys_<T extends Record<string, any>, U extends PropertyKey[]> =
{
[P in keyof T]: {} extends Omit<T, P> ? [P] : Prepend<P, Keys_<Omit<T, P>, U>>
}[keyof T]
const t1: Keys<IMyTable> = ["createdAt", "isDeleted", "id", "title"]
++-instrukcja krotki z automatycznym uzupełnianiem +brak --działania funkcji pomocniczej
Wariant 4: Generator kodu / API kompilatora TS
Tutaj wybrano ts-morph , ponieważ jest to odrobinę prostsza alternatywa opakowania dla oryginalnego API kompilatora TS . Oczywiście możesz również bezpośrednio użyć interfejsu API kompilatora. Spójrzmy na kod generatora:
import {Project, VariableDeclarationKind, InterfaceDeclaration } from "ts-morph";
const project = new Project();
const sourceFile = project.addSourceFileAtPath("./src/IMyTable.ts");
const destFile = project.createSourceFile("./src/generated/IMyTable-keys.ts", "", {
overwrite: true
});
function createKeys(node: InterfaceDeclaration) {
const allKeys = node.getProperties().map(p => p.getName());
destFile.addVariableStatement({
declarationKind: VariableDeclarationKind.Const,
declarations: [{
name: "keys",
initializer: writer =>
writer.write(`${JSON.stringify(allKeys)} as const`)
}]
});
}
createKeys(sourceFile.getInterface("IMyTable")!);
destFile.saveSync();
Po skompilowaniu i uruchomieniu tego pliku tsc && node dist/mybuildstep.js, ./src/generated/IMyTable-keys.tsgenerowany jest plik o następującej zawartości:
const keys = ["id","title","createdAt","isDeleted"] as const;
+rozwiązanie automatycznie generujące +skalowalne dla wielu właściwości +żadna funkcja pomocnicza +krotka -dodatkowy krok kompilacji -wymaga znajomości interfejsu API kompilatora