【问题标题】:TypeScript failing to correctly infer types from generic using keyof as propertyTypeScript 无法使用 keyof 作为属性从泛型中正确推断类型
【发布时间】:2021-05-01 00:43:33
【问题描述】:

我目前正在为验证组件编写一些 typedef 和类,该组件正在构建以验证特定类(下面的“Main”)上的字段。目标:

  1. 每个验证规则 (MainValidationRule) 都应包含它正在验证的键 (T extends keyof Main),以及一个验证相应值并返回指示有效性的标志 ((value: Main[T]) => boolean) 的验证函数。
  2. 为了便于使用验证器,在运行时,验证规则数组将组合成一个对象,将每个字段 (T in keyof Main) 映射到该字段的所有验证规则数组 (Array<MainValidationRule<T>>)。

但是,当我尝试将规则映射到所需的结构(下面的代码)时,TypeScript 会出现类型错误:

Image of type error

我会假设,因为rule.key 的任何给定值都保证rule 将满足约束条件(即,我们知道如果rule.key === 'foo',那么rule 将满足MainValidationRule<'foo'> 类型,并且'bar' 或任何其他可能添加到Main 的键也是如此,这样可以很好地编译。但相反,TypeScript 似乎正在检查 MainValidationRule<keyof Main>所有 可能值是否是 特定 键的有效规则,它失败了(例如)MainValidationRule<'bar'> 不是如果键是 'foo' 则为有效规则 - 尽管事实上我们的约束意味着这永远不可能。

我做错了吗?还是有另一种方法可以让 TypeScript 正确推断出满足约束? Main 类经常使用新属性进行更新,因此手动输入并检查每个可能的变化是不切实际的。代码如下。提前致谢!

type Main = {
    foo: string;
    bar: number;
};

type MainValidationRule<T extends keyof Main> = {
    key: T;
    isValid: (value: Main[T]) => boolean;
};

type MainValidationRulesMap = { [ T in keyof Main ]?: Array<MainValidationRule<T>> };
const mainValidationRulesMap: MainValidationRulesMap = {};
const mainValidationRules: Array<MainValidationRule<keyof Main>> = [];

mainValidationRules.forEach(rule => {
    mainValidationRulesMap[rule.key] = [ rule ]; // type error
});

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    有时最好使用联合类型而不是Foo&lt;keyof Bar&gt;

    为了清楚起见,只需比较我的MainValidationRuleMainValidationRule&lt;keyof Main&gt;

    看起来它们是相等的,但事实并非如此。

    对于 TS,推断简单的联合类型要容易得多。

    代码如下:

    type Main = {
      foo: string;
      bar: number;
    };
    
    type Values<T> = T[keyof T]
    
    type MainValidationRule = Values<{
      [P in keyof Main]: {
        key: P;
        isValid: (value: Main[P]) => boolean;
      }
    }>
    
    type Rules = Array<MainValidationRule>
    
    
    type MainValidationRulesMap = Partial<{ [T in keyof Main]: Rules }>;
    
    const mainValidationRulesMap: MainValidationRulesMap = {};
    
    const mainValidationRules: Rules = [];
    
    mainValidationRules.forEach(rule => {
      mainValidationRulesMap[rule.key] = [rule]; // ok
    });
    

    Playground

    【讨论】:

    • 感谢您的回复!不幸的是,这失去了MainValidationRulesMap 上的键和数组中的规则之间的类型安全性。因此,例如,这将允许:const mainValidationRulesMap: MainValidationRulesMap = { foo: [ { key: 'bar', ... } ] }。目标是能够在键和该键的验证规则之间创建一个类型安全的映射。
    • @SteveHopkinson 你不觉得foo 属性在这里是多余的吗?我不确定您是否需要一个数组,因为您只想在数组中允许一个对象。如果您仍然只想允许一个对象,我将创建一个安全类型,但我认为这不是正确的方法
    • 与实际代码相比,上面的代码被简化了——实际上可能会有更大的规则数组将动态生成(但仍会抛出相同类型的错误)。我不确定 foo 属性冗余是什么意思 - 关键字段对于在运行时过滤和映射规则是必需的。
    猜你喜欢
    • 2017-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-15
    • 2021-11-14
    • 2019-09-17
    • 2021-12-21
    相关资源
    最近更新 更多