Сделать универсальный тип Array <keyof T>, требующий все ключи T - PullRequest
0 голосов
/ 26 августа 2018

Я хотел бы объявить тип, который требует, чтобы все ключи данного типа T были включены в массив, например:

checkKeys<T>(arr: Array<keyof T>): void {
  // do something
}

interface MyType {
  id: string;
  value: number;
}

В настоящее время, если вызов checkKeys<MyType>, TS будетсчитать значение переданным как действительное, если оно содержит какой-либо ключ MyType (id | value):

checkKeys<MyType>(['id', 'value']); // valid

checkKeys<MyType>(['id']); // valid

checkKeys<MyType>(['id', 'values']); // invalid

Можно ли требовать, чтобы все ключи были указаны в массиве?

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Я нашел обходной путь, но на самом деле решение не идеальное:

interface MyType {
  id: string;
  value: number;
}
const myType: MyType = {
   id: '',
   value: 0
};
type ArrType<T> = Array<keyof T>;
function isMyTypeArr<T>(arg: any[]): arg is ArrType<T> {
  return arg.length === Object.keys(myType).length;
}

function checkKeys<T>(arr: ArrType<T>): void {
  if (isMyTypeArr(arr)) {
    console.log(arr.length);
    // some other stuff
  }
}
checkKeys<MyType>(['id', 'x']); // TS error
checkKeys<MyType>(['id']); // no console because of Type Guard
checkKeys<MyType>(['id', 'value']); // SUCCESS: console logs '2'

Идея состоит в том, чтобы создать простой объект, который реализует исходный интерфейс.Нам нужен этот объект, чтобы получить длину ключей для сравнения в isMyTypeArr Type Guard.Тип Guard просто сравните длину массивов - если они имеют одинаковую длину, это означает, что вы предоставляете все свойства.


Edit

Добавлен еще один аналог (более общее) решение - основные отличия:

  • использование класса с параметрами конструктора, который реализует исходный интерфейс;
  • у этого класса есть свойство length (потому что в основном это функция конструктора), мы можем использовать его в нашем Type Guard;
  • мы также должны передать имя класса в качестве второго параметра, чтобыполучить его конструктор аргументов длины.Мы не можем использовать универсальный тип T для этого, потому что скомпилированный JS имеет всю информацию о типе, мы не можем использовать T для нашей цели, проверить этот пост для более подробной информации

Итак, это окончательное решение:

interface IMyType {
  id: string;
  value: number;
}
class MyType implements IMyType {
  constructor(public id: string = '', public value: number = 0) {}
}
type ArrType<T> = Array<keyof T>;
function isMyTypeArr<T>(arg: ArrType<T>, TClass: new () => T): arg is ArrType<T> {
  return arg.length === TClass.length;
}

function checkKeys<T>(arr: ArrType<T>, TClass: new () => T): void {
  if (isMyTypeArr<T>(arr, TClass)) {
    console.log(arr.length);
    // some other stuff
  }
}

checkKeys<MyType>(['id', 'x'], MyType); // TS error
checkKeys<MyType>(['id'], MyType); // no console because of Type Guard
checkKeys<MyType>(['id', 'value'], MyType); // SUCCESS: console logs '2'

Обратите внимание, что эти примеры основаны на Проблемы с TypeScript 13267

пстакже создал демонстрацию stackblitz обоих примеров

0 голосов
/ 26 августа 2018

Вы не можете сделать это с типом массива (по крайней мере, я не знаю способ распространения объединения ключей в тип кортежа, может быть, я просто не знаю об этом).Альтернативой может быть использование литерала объекта для достижения аналогичного эффекта.Синтаксис немного более подробный, но компилятор проверит, что указаны только правильные ключи.Мы будем использовать отображенный тип Record и можем использовать литеральные типы 0 для значений, так как важны только ключи.

function checkKeys<T>(o: Record<keyof T, 0>): void {
     // do something
}

interface MyType {
    id: string;
    value: number;
}

checkKeys<MyType>({ id: 0, value: 0 }); // valid

checkKeys<MyType>({ id: 0 }); // invalid

checkKeys<MyType>({ id: 0, values: 0 }); // invalid
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...