TypeScript:将对象条目映射到类型

Posted

技术标签:

【中文标题】TypeScript:将对象条目映射到类型【英文标题】:TypeScript: Mapping object entries to types 【发布时间】:2020-11-25 16:27:15 【问题描述】:

作为一个新手打字稿用户,我什至无法提出问题,所以请多多包涵。

我正在尝试创建一个键 => [string + valueObject 接口] 字符串和 valueObjects 的映射(作为一种类型),然后有一个函数,它根据传递的键强制执行 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、泛型和枚举进行了一些未受过教育的尝试,但没有成功。

这种类型映射(或查找)是否可能?


更新(工作示例)

经过一番尝试,下面是一个具有潜在解决方案的工作示例:

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】:

2021 年 9 月 17 日更新

我已经删除了所有重复的代码。

我还创建了一个允许参数的联合 - Params。请查看更新示例

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 = 
  [Animal.sound]: 'animal goes sound',
  [Animal.sleep]: 'It sleeps in location',
  [Animal.herd]: 'There is available animals out of expectedCount',
  [Animal.think]: 'Its thinking'
 as const;

type Templates = typeof templates;

type Values<T> = T[keyof T]

type Dictionary = 
  [Prop in keyof TemplateKeys]:
  (key: Prop, params: TemplateKeys[Prop]) => string


type Params =
  Parameters<
    NonNullable<
      Values<Dictionary>
    >
  >

const renderTemplate = (...params: Params) => 
  const [key, ...props] = params;
  if (props !== undefined) 
    // @ts-ignore
    const result = (Object.entries(props) as Array<Params>)
      .reduce<string>((previousValue, [param, value]) =>
        typeof value === 'string'
          ? previousValue.replace('' + param + '', value)
          : previousValue,
        templates[key]
      );

    return result


  
  return templates[key];



renderTemplate(Animal.sleep,  location: 'a hut' ) // ok

renderTemplate(Animal.herd,  expectedCount: 20, available: 10 ) // ok
renderTemplate(Animal.sound,  location: 'sd' ) // expected error

我为键创建了Animal 枚举。不用担心,枚举不会包含在您的捆绑包中。

这是payground的链接 这是Object.entries typings的链接

说明:

Dictionary - 主要的实用程序类型。它使用Animal 键和函数作为值创建一个对象。这个函数已经考虑了特定键的允许道具。

例子:

type Dictionary = 
    "animal.sound": (key: Animal.sound, params: 
        animal: string;
        sound: string;
    ) => string;
    "animal.sleep": (key: Animal.sleep, params: 
        location: string;
    ) => string;
    "animal.herd": (key: Animal.herd, params: 
        expectedCount: number;
        available: number;
    ) => string;
    "animal.think"?: ((key: Animal.think, params: string | undefined) => string) | undefined;

现在,我们需要将此数据结构转换为具有两个元素的元组。第一个元素 - 一个键,第二个元素 - 是允许的参数。

你可能会问:为什么我们需要一个元组?因为rest参数是一个元组。

所以我们只需要借助@9​​87654330@ 实用程序获取所有值,并借助内置实用程序Parameters 获取它们的参数:

type Params =
  | [Animal.sound, 
    animal: string;
    sound: string;
  ]
  | [Animal.sleep, 
    location: string;
  ]
  | [Animal.herd, 
    expectedCount: number;
    available: number;
  ]
  | [Animal.think, string | undefined]

type Params =
  Parameters<
    NonNullable<
      Values<Dictionary>
    >
  >

【讨论】:

以上是关于TypeScript:将对象条目映射到类型的主要内容,如果未能解决你的问题,请参考以下文章

json 2 typescript 映射给出类型错误

类型化对象联合上的详尽映射

Typescript Conditional Types 结合映射类型文档示例说明

打字稿:如何将对象映射到类型?

将 JSON 对象映射到 TypeScript 对象

在 TypeScript/React 的映射函数中转换正确的类型