我有以下类型
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''
我不知道如何解决这个问题。
答案 0 :(得分:2)
考虑以下代码-
export type Groups = {
group1: {
prop1?: string;
prop2?: number;
};
group2: {
prop3?: string;
};
};
type Z = Groups[keyof Groups]
type X = keyof Z
类型 Z
是来自 Groups
的所有可能类型的联合。联合类型的 keyof
返回 never
。这就是为什么 prop
中 AllGroups
的类型是 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']
},
];
答案 1 :(得分:2)
考虑下一个例子:
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
缺点:在这里你应该使用除了类型推断之外什么都不做的函数。
我认为它会被 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;
但这就是我验证函数参数的方式。
如果你对解释感兴趣,请ping我,我会更新答案