Принимать только ключи данного объекта

Я пытаюсь создать класс TypeScript, который инициализируется объектом и имеет метод, который может принимать только ключи этого объекта в качестве аргументов. Так:

class MyClass {
  properties = {};

  constructor(properties) {
    this.properties = properties;
  }

  // Passed propNames should only be keys of this.properties
  pick(...propNames) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

Это похоже на Эта проблема, но я не могу понять, как его применить в этом случае, так как свойства передаются извне.

const props = { key: 'value', key2: 'value2' };
interface PropKeys {
  key: string;
  key2: string;
}
type KeyName = keyof(PropKeys);

// But what do I do to the class to get this to work?
const instance = new MyClass(props);
instance.pick('key', 'key2'); // Great
instance.pick('key3'); // Should throw a type error

Это возможно? Есть ли способ сделать это без явного определения InstanceKeys, а вместо этого получить их из реквизитов, переданных при инициализации экземпляра?

Я пытаюсь обернуть голову вокруг дженериков и подумал, может быть, что-то вроде этого:

class MyClass {
  properties = {};

  constructor<Type>(properties: Type) {
    this.properties = properties;
    type TypeKeys = keyof(Type);
  }
  
  pick(...propNames: TypeKeys[]) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

Но это вызывает две ошибки типа:

  • На <Type>: «Параметры типа не могут отображаться в объявлении конструктора».
  • На TypeKeys[]: «Не удается найти имя TypeKeys». (Я имею в виду, имеет смысл; это выходит за рамки.)

ОБНОВИТЬ: Это кажется ближе, но я столкнулся с проблемой, когда свойства сначала определяются в классе (над конструктором):

class MyClass<PropType extends Properties> {
  properties: PropType = {};

  constructor(properties: PropType) {
    this.properties = properties;
  }

  pick(...propNames: Array<keyof(PropType)>) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {});
  }
}

Ошибка TS, с которой я сталкиваюсь в этой строке,

Type '{}' is not assignable to type 'PropType'. '{}' is assignable to the constraint of type 'PropType', but 'PropType' could be instantiated with a different subtype of constraint 'Properties'

Проблема здесь в том, что любой переданный properties может иметь свои собственные ключи, но должен быть экземпляром типа Properties, который ограничивает значения.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
3
0
37
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш общий тип должен идти в объявлении class, а не в его конструкторе. Тогда keyof Type должен быть анонимным типом. Вам также нужно ввести properties, чтобы TypeScript знал, что его можно индексировать с помощью keyof Type, что я и сделал в этом примере, присвоив ему тип Partial<Type>.

Я также использовал утверждение типа, чтобы начальный объект {} вашего reduce был типизирован как Partial<Type>, поэтому TypeScript поймет, как его индексировать после его создания.

class MyClass<Type> {
  properties: Partial<Type> = {};

  constructor(properties: Type) {
    this.properties = properties;
  }
  
  pick(...propNames: (keyof Type)[]) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {} as Partial<Type>);
  }
}

Площадка для TypeScript

Другие вопросы по теме