Безпечні варіанти
Створення масиву або кортежу ключів від інтерфейсу з перевіркою безпеки під час компіляції вимагає трохи творчості. Типи стираються під час виконання, і типи об’єктів (невпорядковані, іменовані) не можуть бути перетворені в типи кортежів (впорядковані, неіменовані), не вдаючись до непідтримуваних технік .
Порівняння з іншими відповідями
Всі запропоновані тут варіанти розглядають / запускають помилку компіляції у випадку повторення або відсутності елементів кортежу з заданим типом об’єкта посилання IMyTable. Наприклад, оголошення типу масиву (keyof IMyTable)[]не може виявити ці помилки.
Крім того, вони не потребують певної бібліотеки (останній варіант використовує ts-morph, який я вважав би загальним обгорткою компілятора), видають тип кортежу на відміну від об’єкта (лише перше рішення створює масив) або широкого типу масиву (порівняйте з ці відповіді ) і, нарешті , не потребують занять .
Варіант 1: Простий набраний масив
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 })
+найпростіший +-посібник із -масивом автозаповнення , без кортежу
Дитячий майданчик
Якщо вам не подобається створювати запис, подивіться на цю альтернативу з Setтипами тверджень та твердженнями .
Варіант 2: Кортеж з допоміжною функцією
function createKeys<T extends readonly (keyof IMyTable)[] | [keyof IMyTable]>(
t: T & CheckMissing<T, IMyTable> & CheckDuplicate<T>): T {
return t
}
++-посібник з кортежами з автоматичним заповненням +-більш досконалих, складних типів
Дитячий майданчик
Пояснення
createKeysробить перевірки під час компіляції , об'єднуючи тип параметра функції з додатковими типами тверджень, які видають помилку для непридатного введення. (keyof IMyTable)[] | [keyof IMyTable]є "чорною магією" способом примусити зробити висновок кортежу замість масиву з боку викликаного. Крім того, ви можете використовувати твердження const /as const з боку абонента.
CheckMissingперевіряє, якщо Tпропустив ключі від 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 }>
Примітка: T & "Error: missing keys"саме для приємних помилок IDE. Ви також могли писати never. CheckDuplicatesперевіряє подвійні елементи кортежу:
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]>
Примітка: Більше інформації про перевірку унікальних предметів у кортежах міститься в цій публікації . За допомогою TS 4.1 ми також можемо називати відсутні ключі в рядку помилок - погляньте на цей майданчик .
Варіант 3: рекурсивний тип
З версією 4.1 TypeScript офіційно підтримує умовні рекурсивні типи , які також можуть бути використані тут. Хоча обчислення типів є дорогими через комбінаційну складність - продуктивність значно погіршується для більш ніж 5-6 елементів. Я перелічую цю альтернативу для повноти ( дитячий майданчик ):
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"]
+Кортеж +-керівництво з автозаповненням +НЕ допоміжна функція --продуктивності
Варіант 4: API генератора коду / компілятора TS
Тут вибрано ts-morph , оскільки це трохи простіша обгортка, альтернатива оригінальному API компілятора TS . Звичайно, ви також можете використовувати API компілятора безпосередньо. Давайте розглянемо код генератора:
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();
Після компіляції та запуску цього файлу генерується tsc && node dist/mybuildstep.jsфайл ./src/generated/IMyTable-keys.tsіз таким вмістом:
const keys = ["id","title","createdAt","isDeleted"] as const;
+автоматичні породжує рішення +масштабується для декількох властивостей +НЕ допоміжної функції +кортежу -додаткової збірки кроку -не потрібно знайомство з компілятором API