TypeScript: сопоставление записей объектов с типами - PullRequest
0 голосов
/ 05 августа 2020

Как начинающий пользователь машинописного текста, у меня возникли проблемы даже с формулировкой вопроса, поэтому, пожалуйста, подождите. (как тип), а затем иметь функцию, которая обеспечивает выполнение интерфейса valueObject на основе переданного ключа.

Я считаю, что лучше всего это объяснить на примере:

// This is an pseudo example stub, not actually working

type ReplaceableWith<T> = string;
//                   ^ the type I'd like to enforce as the argument

const templates = {
  // templateId    // template               // define somehow the interface required for this template
  'animal.sound': 'A {animal} goes {sound}' as ReplaceableWith<{ animal: string; sound: string}>
};

function renderTemplate(
  templateId , // must be a key of templates
  params // must match value object type, based on templateId
): string {
  let rendered = templates[templateId];
  for (const [key, value] of Object.entries(params)) {
    // replace keys from template with values
    rendered = rendered.replace('{' + key + '}', value);
  }
  return rendered;
}

const a = renderTemplate('animal.sound', { animal: 'Dog', sound: 'woof' })
//    ^ a = 'A Dog goes woof'
const b = renderTemplate('animal.sound', { name: 'Some' });
//                                         ^ should throw TS error

Очевидно, это пример не работает, но я думаю, что он демонстрирует то, что я пытаюсь достичь. Я безуспешно предпринял несколько необразованных попыток с keyof, generics и enums.

Возможно ли такое сопоставление типов (или поиск)?

Обновление (рабочий пример)

После некоторой игры, вот рабочий пример с потенциальным решением:

type TemplateKeys = {
  'animal.sound': { animal: string; sound: string };
  'animal.sleep': { location: string };
  'animal.herd': { expectedCount: number; available: number };
  'animal.think': undefined;
};

const templates: {[key in keyof TemplateKeys]: string} = {
  'animal.sound': '{animal} goes {sound}',
  'animal.sleep': 'It sleeps in {location}',
  'animal.herd': 'There is {available} animals out of {expectedCount}',
  'animal.think': 'Its thinking'
};

function renderTemplate<K extends keyof TemplateKeys>(key: K, params?: TemplateKeys[K]): string {
  if (params !== undefined) {
    //@ts-ignore
    return Object.entries(params).reduce((previousValue: string, [param, value]: [string, any]) => {
      return previousValue.replace('{' + param + '}', value);
    }, templates[key]);
  }
  return templates[key];
}

console.log(renderTemplate('animal.sound', { animal: 'Dog', sound: 'woof' }));
console.log(renderTemplate('animal.sleep', { location: 'a hut' }));
console.log(renderTemplate('animal.herd', { expectedCount: 20, available: 10 }));
console.log(renderTemplate('animal.think'));

Выводы:

[LOG]: Dog goes woof 
[LOG]: It sleeps in a hut 
[LOG]: There is 10 animals out of 20 
[LOG]: Its thinking 

Хотя это работает, у него есть две проблемы:

  1. Мне нужно дважды определить ключи (в интерфейсе и экземпляре).
  2. Интерфейс параметров и сообщение разделены, в идеале они должны быть вместе.

1 Ответ

0 голосов
/ 05 августа 2020

Взгляните на код ниже:

const enum Animal {
  sound = 'animal.sound',
  sleep = 'animal.sleep',
  herd = 'animal.herd',
  think = 'animal.think',
}

type TemplateKeys = {
  [Animal.sound]: { animal: string; sound: string };
  [Animal.sleep]: { location: string };
  [Animal.herd]: { expectedCount: number; available: number };
  [Animal.think]?: string;
};

const templates: Record<Animal, string> = {
  [Animal.sound]: '{animal} goes {sound}',
  [Animal.sleep]: 'It sleeps in {location}',
  [Animal.herd]: 'There is {available} animals out of {expectedCount}',
  [Animal.think]: 'Its thinking'
};

interface TemplateOverloading {
  (key: Animal.sound, params: TemplateKeys[Animal.sound]): string;
  (key: Animal.sleep, params: TemplateKeys[Animal.sleep]): string;
  (key: Animal.herd, params: TemplateKeys[Animal.herd]): string;
  (key: Animal.think, params?: TemplateKeys[Animal.think]): string;
}

const renderTemplate: TemplateOverloading = (key: Animal, params?: TemplateKeys[Animal]): string => {
  if (params !== undefined) {
    //@ts-ignore
    return Object.entries(params).reduce((previousValue: string, [param, value]: [string, any]) => {
      return previousValue.replace('{' + param + '}', value);
    }, templates[key]);
  }
  return templates[key];
}

const x = renderTemplate(Animal.sound, { animal: 'Dog', sound: 'woof' });
console.log(renderTemplate(Animal.sleep, { location: 'a hut' }));
console.log(renderTemplate(Animal.herd, { expectedCount: 20, available: 10 }));
console.log(renderTemplate(Animal.think));

Я создал Animal перечисление для ключей. Не волнуйтесь, enum не будет включен в ваш пакет. Кроме того, я добавил TemplateOverloading, чтобы убедиться, что функция получит подходящие параметры

Вот ссылка на payground Вот ссылка для Object.entries

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...