打字稿根据前一个参数的值推断可能的对象键? [英] Typescript infer possible object keys based on value of previous argument?

查看:21
本文介绍了打字稿根据前一个参数的值推断可能的对象键?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建翻译服务.用于翻译标签的函数应提供对可能标签的类型检查,但也应提供基于给定标签的替换对象的类型检查.

I'm trying to build a translation service. The function that is used to translate tags should provide type checking on possible tags, but also on the replacement object based on the given tag.

我有一个标签列表对象,它描述了可能的标签和一个应出现在已翻译字符串中的占位符列表.

I have a tag list object that describes the possible tags and a list of placeholders that should be present inside the translated string.

export const TagList = {
    username_not_found: ['username']
};

在这种情况下,关键是字典应该实现的标签名称.该值是一个占位符列表,这些占位符应该出现在翻译后的字符串中.

The key in this case is the tag name that a dictionary should implement. The value is a list of placeholders that should be present in the translated string.

字典看起来有点像这样:

A dictionary looks somewhat like this:

// Note: The type declaration of keys don't work this way (key should be number or string). Not sure how I should implement this...
const en: {[name: keyof (typeof TagList)]: string} = {
    "username_not_found": "The user {username} does not exist"
}

用于翻译标签的方法如下:

The method used to translate tags with works like this:

this.trans("username_not_found", {username: "someone@example.com"});

我想要实现的是在我的 IDE 中对占位符对象进行类型检查(自动完成),以强制配置所有占位符.

What I'm trying to achieve is type checking (autocompletion) in my IDE for the placeholder object to enforce that all placeholders are configured.

例如:

// This is wrong: "username" placeholder is not specified.
this.trans("username_not_found", {});

// This is also wrong: "foobar" is a non-existing placeholder.
this.trans("username_not_found", {foobar: "42"});

// This is good:
this.trans("username_not_found", {username: "someone@example.com"});

目前我使用 keyof (typeof TagList) 作为 tagName 的参数类型.我不确定这是否是正确的方法,但它有效.我现在正在寻找一种方法来根据第一个参数中给定的值来推断第二个参数的对象结构.

Currently I'm using keyof (typeof TagList) as the argument type for tagName. I'm not sure if this is the correct way of doing this, but it works. I'm now looking for a way to infer the object stucture of the second argument based on the value given in the first argument.

我试图避免维护多个可能标签的列表(例如,必须同时在接口和对象中声明它们).

I'm trying to avoid having to maintain multiple lists of possible tags (e.g. having to declare them in both an interface and an object at the same time).

提前致谢!

推荐答案

首先,你需要让 TagList 不可变.

First of all, you need to make TagList immutable.

然后我只是根据键创建了文字类型.非常类似于 Array.prototype.reduce

Then I just created literal type based on key. Very similar to Array.prototype.reduce

export const TagList = {
    username_not_found: ['username'],
    username_found: ['name'],
    batman: ['a', 'b']
} as const;

type Elem = string

type Reducer<
    Arr extends ReadonlyArray<Elem>, // array
    Result extends Record<string, any> = {} // accumulator
    > = Arr extends [] ? Result // if array is empty -> return Result
    : Arr extends readonly [infer H, ...infer Tail] // if array is not empty, do recursive call with array Tail and Result
    ? Tail extends ReadonlyArray<Elem>
    ? H extends Elem
    ? Reducer<Tail, Result & Record<H, string>>
    : never
    : never
    : never;

type TagList = typeof TagList;

const trans = <Tag extends keyof TagList, Props extends Reducer<TagList[Tag]>>(tag: Tag, props: Props) => null as any

trans("username_not_found", { username: "someone@example.com" }); // ok
trans("username_found", { name: "John" }); // ok
trans("batman", { a: "John", b: 'Doe' }); // ok

trans("username_not_found", { name: "someone@example.com" }); // expected error
trans("username_found", { username: "John" }); // expected error

这里的主要目标是将元组 ['username'] 转换为 { username: string }

The main goal here is to convert tuple ['username'] into { username: string }

你会如何在纯 js 中做到这一点?

How would you do it in pure js?

['username'].reduce((acc, elem) => ({ ...acc, [elem]: 'string' }), {})

我使用几乎相同的算法,但使用递归而不是迭代.

I'm using almost the same algorithm, but recursion instead of iteration.

这是 Reducer 实用程序类型的 js 模拟

This is js analogue of Reducer utility type

const reducer = (arr: ReadonlyArray<Elem>, result: Record<string, any> = {}): Record<string, any> => {
    if (arr.length === 0) {
        return result
    }

    const [head, ...tail] = arr;

    return reducer(tail, { ...result, [head]: 'string' })
}

您可以在我的博客

这篇关于打字稿根据前一个参数的值推断可能的对象键?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆