从可区分联合派生 TypeScript 接口

Derive TypeScript interface from discriminiated union

考虑以下 TypeScript 代码:

type AccountType = "staff" | "user"

type StaffAccountName = "owner" | "CEO" | "accountant"

type UserIdentifier =
  | { accountType: "user"; id: number }
  | { accountType: "staff"; id: StaffAccountName };

我现在想制作一个字典来存储每个用户的年龄,格式为
dict[accountType][id] = "age"

如何动态输入这样的字典?我在想

type Dictionary<K,V> = Partial<Record<K,V>>

type AgeDict<UI extends UserIdentifier> = {
   [UI.accountType]: Dictionary<UI.id, number>
}

...这显然行不通。


我真的很想避免重新定义允许的类型对,例如:

type AgeDict<UI extends UserIdentifier> = {
   "user": Dictionary<number, number>,
   "staff": Dictionary<StaffAccountName, number>
}

因为我希望他们能够依赖单一的事实来源。


编辑: Jcalz 的回答在技术上允许我创建界面,但是 I get an error 当我尝试访问它时:

const getAge = (identifier: UserIdentifier, dict: AgeDict) => dict[identifier.accountType][identifier.id]
//                                                            ^
// Element implicitly has an 'any' type because expression of type 'number | "owner" | "CEO" | "accountant"' can't be used to index type 'Partial<Record<StaffAccountName, number>> | Partial<Record<number, number>>'.
//   No index signature with a parameter of type 'number' was found on type 'Partial<Record<StaffAccountName, number>> | Partial<Record<number, number>>'.(7053)

我还需要能够推断出上述函数有效的解决方案。

我建议使用映射类型。特别是,您可以使用 TypeScript 4.1 中引入的 mapped type key remapping via as,如下所示:

type AgeDict = {
    [UI in UserIdentifier as UI['accountType']]: Dictionary<UI['id'], number>
}

这在概念上与您所做的相同。您只是错过了它需要是一个映射类型(而不是计算键/索引签名的东西)并且要在您需要 a lookup type 的类型中查找 属性 ,它使用方括号表示法 UI['accountType'] 而不是像 UI.accountType 这样的点分符号(不支持,因为它与类型命名空间冲突)。

让我们看看它是否如您所愿:

/* type AgeDict = {
    user: Partial<Record<number, number>>;
    staff: Partial<Record<StaffAccountName, number>>;
} */

const a: AgeDict = {
    staff: {
        CEO: 50,
        owner: 45,
        accountant: 40
    },
    user: {
        1: 35,
        2: 30,
    }
}

看起来不错。

Playground link to code