【问题标题】:Typescript keyof of nested object嵌套对象的打字稿键
【发布时间】:2021-06-28 04:25:26
【问题描述】:

我有以下类型

export type Groups = {
  group1: {
    prop1?: string;
    prop2?: number;
  };
  group2: {
    prop3?: string;
  };
};

我想定义基于该组构建的另一种类型

type AllGroups = {
  groupName: keyof Groups;
  prop: keyof Groups[keyof Groups];
  values: string[];
}[];

然后我定义对象

const allGroups: AllGroups = [
  {
    groupName: 'group1',
    prop: 'prop1',
    values: ['a']
  },
];

问题是打字稿在prop 字段上抱怨。 groupName 字段很好。

TS2322: Type 'string' is not assignable to type 'never'. The expected type comes from property 'prop''

我不知道如何处理这个问题。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    考虑下一个例子:

    type Result = {
        prop1?: string | undefined;
        prop2?: number | undefined;
    } | {
        prop3?: string | undefined;
    }
    
    type Keys = keyof Result // never
    

    上述联合中没有公共键,因此 TS 无法确定它应该返回给您哪些键。这就是您收到错误消息的原因。

    要处理这种联合,您可以使用@Shivam Singla 的方式或这种通用方式:

    // credits goes to Titian Cernicova-Dragomir 
    //https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
    
    type UnionKeys<T> = T extends T ? keyof T : never;
    type StrictUnionHelper<T, TAll> =
        T extends any
        ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
    
    type StrictUnion<T> = StrictUnionHelper<T, T>
    

    所以,不要写:

    keyof Groups[keyof Groups];
    

    你可以写:

    keyof StrictUnion<Groups[keyof Groups]>
    

    这是我的完整解决方案:

    export type Groups = {
        group1: {
            prop1?: string;
            prop2?: number;
        };
        group2: {
            prop3?: string;
        };
    };
    
    type UnionKeys<T> = T extends T ? keyof T : never;
    type StrictUnionHelper<T, TAll> =
        T extends any
        ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
    
    type StrictUnion<T> = StrictUnionHelper<T, T>
    
    
    interface AllGroups {
        groupName: keyof Groups;
        prop: keyof StrictUnion<Groups[keyof Groups]>;
        values: string[];
    };
    
    const allGroups = <T extends AllGroups, U extends {
        0: ReadonlyArray<T>,
    }[
        T['groupName'] extends keyof Groups
        ? T['prop'] extends keyof Groups[T['groupName']]
        ? 0 : never : never]>(arr: ReadonlyArray<T> & U) => arr;
    
    const result = allGroups(
        [{
            groupName: 'group1',
            prop: 'prop2',
            values: ['a']
        },
        {
            groupName: 'group1',
            prop: 'prop1',
            values: ['a']
        }]) // ok
    
    const result2 = allGroups(
        [{
            groupName: 'group1',
            prop: 'prop2',
            values: ['a']
        },
        {
            groupName: 'group1',
            prop: 'prop3', // expected error
            values: ['a']
        }]) // ok
    

    Playground

    缺点:在这里你应该使用除了类型推断之外什么都不做的函数。

    我假设它会被 V8 引擎优化。

    您也可以使用closure webpack plugin 来摆脱函数调用。

    但是,我投票支持 @Shivam Singla 的解决方案,因为没有函数开销。

    我刚刚发布了这个解决方案给你一些替代方法。

    这个类型有点复杂:

    const allGroups = <T extends AllGroups, U extends {
        0: ReadonlyArray<T>,
    }[
        T['groupName'] extends keyof Groups
        ? T['prop'] extends keyof Groups[T['groupName']]
        ? 0 : never : never]>(arr: ReadonlyArray<T> & U) => arr;
    

    但这就是我验证函数参数的方式。

    如果您对解释感兴趣,请联系我,我会更新答案

    【讨论】:

    • 不错,这看起来很不错。感谢您的辛勤工作和解释!
    【解决方案2】:

    问题

    考虑以下代码-

    export type Groups = {
      group1: {
        prop1?: string;
        prop2?: number;
      };
      group2: {
        prop3?: string;
      };
    };
    
    type Z = Groups[keyof Groups]
    
    type X = keyof Z
    

    Playground

    类型ZGroups 中所有可能类型的联合。联合类型的keyof 返回never。这就是为什么AllGroupsprop 的类型是never

    解决方案

    我们从Groups 创建一个联合类型,然后将它们用作Discriminated Unions

    export type Groups = {
      group1: {
        prop1?: string;
        prop2?: number;
      };
      group2: {
        prop3?: string;
      };
    };
    
    type AllGroups = {
      [K in keyof Groups]: {
        groupName: K
        prop: keyof Groups[K]
        values: string[]
      }
    }[keyof Groups][]
    
    const allGroups: AllGroups = [
      {
        groupName: 'group1',
        prop: 'prop2',
        values: ['a']
      },
      {
        groupName: 'group1',
        prop: 'prop3', // error expected
        values: ['a']
      },
      {
        groupName: 'group2',
        prop: 'prop3',
        values: ['a']
      },
      {
        groupName: 'group2',
        prop: 'prop1', // error expected
        values: ['a']
      },
    ];
    

    Playground

    【讨论】:

    • 很遗憾,如果你愿意groupName: 'group2' will allow prop1`
    • @captain-yossarian 哦,我误解了这个问题。我的错。修复它!
    • 无论如何,您的解决方案很好。为了正确推断groupName属性,我相信我们需要将它包装在函数中
    • 或者,我们可以区分联合类型。
    • 正确,如果你为每个组定义类型,应该可以
    猜你喜欢
    • 2020-02-14
    • 2020-11-30
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多