【问题标题】:Typescript: Base a type on the corresponding Type in a Map打字稿:基于地图中相应类型的类型
【发布时间】:2021-09-15 12:51:21
【问题描述】:

我正在使用 Firestore,我想将我的数据库中的路径与正确的接口相关联。

假设我有 3 条可能的路径。以下是这些路径的类型:

type FsPath =
  `badges/${string}` |
  'entities' |
  `entities/${string}`;

在路径entitiesentities/${string}我只想存储ServerEntity类型的数据,而在路径badges/${string}我只想存储ServerBadge类型的数据;

interface ServerEntity {
  name: string;
  age: number;
}

interface ServerBadge {
  level: number;
  label: string;
}

现在假设我有这个功能

setValue(path: FsPath, value: any) {
  // I want to know how to replace the any with a deduced type from the FsPath of "path"
}

我应该写什么而不是“any”以确保我不能执行以下操作

const badge: ServerBadge = {
  level: 4,
  label: 'Best badge'
};

setValue('entities/qs89sdjdziU', badge); // I want this to raise an TS error

如何在我的 FsPath 和我的接口 ServerBadge、ServerEntity 之间创建一个“映射”(不确定术语)? 那么如何检查这个“地图”以在 setValue() 中键入“值”键?

希望问题足够清楚。

谢谢

编辑:添加了 ServerBadge 和 ServerEntity 类型

@captain-yossarian 回答后编辑:

有没有办法创建一个 PathMap 但将整个路径作为键来代替?

type PathMap = {
  'badges': ServerBadge[],
  `badges/${string}`: ServerBadge,
  'entities': ServerEntity[],
  `entities/${string}`: ServerEntity
}

我知道这行不通,因为类型键不能是 TS 类型。那么如何在 2 种类型之间创建 Map 呢?

编辑添加了带有子集合问题的修改后的游乐场:

Playground

【问题讨论】:

  • 我不确定我是否理解您对 value 的期望。请分享ServerBadge类型
  • 我已经更新了问题。抱歉,如果不清楚。我希望值的类型基于 FsPath 和接口之间的某种映射。因此,如果有人试图在路径“entities/smth”上设置徽章,我想提出一个错误
  • 为什么setValue('entities/qs89sdjdziU', badge); 会导致错误?期望值是多少?
  • 因为我想。我想为我的代码添加安全性。我不希望我的数据库在实体集合中有徽章。
  • 这是误会。如果你提供/entities 应该valueServerEntity 吗?

标签: typescript


【解决方案1】:

考虑这个例子:

interface ServerEntity {
    name: string;
    age: number;
}

interface ServerPlace {
    title: string;
    city: string;
}

interface ServerBadge {
    level: number;
    label: string;
}

type FsPath =
    | 'badges'
    | `badges/${string}`
    | 'entities'
    | `entities/${string}`
    | `entities/${string}/places`
    | `entities/${string}/places/${string}`;


type FsMap = {
    'entities': ServerEntity,
    'badges': ServerBadge
}

type Validate<T> = T extends FsPath ? T : never

type GetPrefix<T extends string> =
    (T extends `${infer Fst}/${infer _}`
        ? Validate<Fst>
        : (T extends `${infer Fst}`
            ? Validate<Fst> : never
        )
    );

type GetValue<T extends FsPath> = GetPrefix<T> extends keyof FsMap ? FsMap[GetPrefix<T>] : never

function setValue<P extends FsPath>(path: P, value: GetValue<P>) { }

const badge: ServerBadge = {
    level: 4,
    label: 'Best badge'
};

const entity: ServerEntity = {
    name: 'hello',
    age: 42
}

setValue('entities/qs89sdjdziU', entity); // ok
setValue('badges', badge); // ok

setValue('entities/qs89sdjdziU', badge); // error

Playground

PathMap 只是一个字典,没什么特别的。

GetValue 将路径分成两部分 ${first part}/${second part}。然后,它检查first part 是否是字典的键。如果是 - 通过键获取值,否则 - 从不返回。

您可以在我的article 中找到更多关于函数参数验证的有趣示例

【讨论】:

  • 哇太棒了。谢谢
  • 哦,废话。但它不适用于子集合。例如,路径可以是实体/ID_HERE/places/ID_HERE。但只有 ServerPlace 应该是此路径的有效类型。
  • @Maslow 请在问题中提供更多信息以及预期和意外行为的适当示例
  • 真的很好。谢谢!我已经使用子集合问题的更新游乐场更新了问题。有解决办法吗?
  • 您还有其他要求吗?我的意思是,是否有任何允许路径的模式?
猜你喜欢
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-19
  • 1970-01-01
  • 2021-07-08
  • 1970-01-01
  • 2021-11-25
相关资源
最近更新 更多