Упомянутый вами вариант использования с типом кортежа для Object.keys()
сопряжен с опасностью, и я рекомендую не использовать его.
Первая проблема заключается в том, что типы в TypeScript не "точны" .То есть, только потому, что у меня есть значение типа Person
, это не означает, что значение содержит only свойства name
и age
.Представьте себе следующее:
interface Superhero extends Person {
superpowers: string[]
}
const implausibleMan: Superhero = {
name: "Implausible Man",
age: 35,
superpowers: ["invincibility", "shape shifting", "knows where your keys are"]
}
declare const randomPerson: Person;
const people: Person[] = [implausibleMan, randomPerson];
Object.keys(people[0]); // what's this?
Object.keys(people[1]); // what's this?
Обратите внимание, что implausibleMan
- это Person
с дополнительным свойством superpowers
, а randomPerson
- это Person
с тем, кто знает, какие дополнительные свойства.Вы просто не можете сказать, что Object.keys()
, действующий на Person
, создаст массив с только известными ключами.Это основная причина, по которой такие запросы функций продолжают получать отклонены .
Вторая проблема связана с упорядочением ключей.Даже если вы знали, что имеете дело с точными типами, которые содержат все и только объявленные свойства интерфейса, вы не можете гарантировать , что ключи будут возвращены Object.keys()
в том же порядке, что и интерфейс.Например:
const personOne: Person = { name: "Nadia", age: 35 };
const personTwo: Person = { age: 53, name: "Aidan" };
Object.keys(personOne); // what's this?
Object.keys(personTwo); // what's this?
Наиболее разумные движки JS , вероятно, вернут вам свойства в том порядке, в котором они были вставлены, но вы не можете на это рассчитывать.И вы, конечно, не можете рассчитывать на тот факт, что порядок вставки совпадает с порядком свойств интерфейса TypeScript.Так что вы, скорее всего, будете рассматривать ["age", "name"]
как объект типа ["name", "age"]
, что, вероятно, не очень хорошо.
ВСЕ, ЧТО СКАЗАЛ, Я люблю возиться с системой типов, поэтому я решил сделать кодпревратить объединение в кортеж так же, как ответ Мэтта Маккатчена на другой вопрос .Это также чревато опасностями, и я рекомендую против этого.Предостережения ниже.Вот оно:
// add an element to the end of a tuple
type Push<L extends any[], T> =
((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
{ [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never
// convert a union to an intersection: X | Y | Z ==> X & Y & Z
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
// convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
// convert a union to a tuple X | Y => [X, Y]
// a union of too many elements will become an array instead
type UnionToTuple<U> = UTT0<U> extends infer T ? T extends any[] ?
Exclude<U, T[number]> extends never ? T : U[] : never : never
// each type function below pulls the last element off the union and
// pushes it onto the list it builds
type UTT0<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT1<Exclude<U, A>>, A> : []
type UTT1<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT2<Exclude<U, A>>, A> : []
type UTT2<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT3<Exclude<U, A>>, A> : []
type UTT3<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT4<Exclude<U, A>>, A> : []
type UTT4<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT5<Exclude<U, A>>, A> : []
type UTT5<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTTX<Exclude<U, A>>, A> : []
type UTTX<U> = []; // bail out
Давайте попробуем:
type Test = UnionToTuple<keyof Person>; // ["name", "age"]
Похоже, это работает.
Предостережения: вы не можете сделать это программно для объединения произвольного размера.TypeScript не позволяет вам перебирать типы объединений , поэтому любое решение здесь будет выбирать некоторый максимальный размер объединения (скажем, шесть констант) и обрабатывать объединения до этого размера.Мой несколько сложный код выше разработан так, что вы можете расширить этот максимальный размер в основном путем копирования и вставки.
Еще одна оговорка: это зависит от способности компилятора анализировать перегруженные сигнатуры функций в условных типах по порядку, а также от способности компилятора преобразовывать объединение в перегруженную функцию при сохранении порядка.Ни одно из этих поведений не обязательно будет работать одинаково, поэтому вам нужно проверять это каждый раз, когда выходит новая версия TypeScript.
Заключительное предупреждение: он не был проверен, поэтомуможет быть полон всевозможных ? интересных подводных камней, даже если вы сохраняете постоянную версию TypeScript.Если вы серьезно относитесь к использованию подобного кода, вам придется подвергнуть его большому количеству испытаний, прежде чем даже подумать об использовании его в рабочем коде.
В заключение, не делайте ничего, что я показал здесь.Хорошо, надеюсь, это поможет.Удачи!