[英]How to create a RequireOnlyOne nested type in TypeScript?
我经常看到这个代码片段:
type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?:
Required<Pick<T, K>>
& Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys]
在这里你可以找到我提出的问题。
它有效,但是,我有这样的结构:
export interface MenuItems {
firstLevel: {
secondLevel: ['one', 'two']
};
anotherFirstLevel: {
anotherSecondLevel: ['one', 'two'],
oneMoreSecondLevel: null
};
}
我需要为第一级和第二级应用RequireOnlyOne
,但我无法确定RequireOnlyOne
类型的更改内容,因此它适用于每个 firstLevel 键,但也适用于 secondLevel 键。 就像现在一样,我只能选择一个 firstLevel,但可以选择该 firstLevel 的多个 secondLevel。
我还尝试用一个对象组合一个新类型,该对象的键可以是RequireOnlyOne<keyof MenuItems>
和一个也使用RequireOnlyOne
作为值的值,但无法实现。
我想要的示例,将所需类型称为customType
:
const workingObject: customType = {
firstLevel: { // Just one property of the first level
secondLevel: ['one', 'two'] // Just one property of the second level
};
}
const errorObject: customType = {
firstLevel: {
secondLevel: ['one', 'two']
};
anotherFirstLevel: { // Should not work as I am including 2 properties for the first level
anotherSecondLevel: ['one', 'two']
};
}
const anotherErrorObject: customType = {
anotherFirstLevel: {
anotherSecondLevel: ['one', 'two'],
oneMoreSecondLevel: null // Should not work neither as I am including 2 properties for second level
};
}
如果对象具有多个第一级属性和/或多个第二级属性,则该类型应引发错误。 使用建议的RequireOnlyOne
类型,我可以实现这一点,但仅适用于第一级,但我需要第一级和第二级的相同效果。
有任何想法吗?
IMO 这是一个非常复杂的问题,我建议寻找替代方案并重构代码。
否则,您可能会发现这很有帮助:
type RequireOnlyOneUnion<T, Keys extends KeysOfUnion<T> = KeysOfUnion<T>> = Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];
export interface MenuItems {
firstLevel: {
secondLevel: ["one", "two"];
};
anotherFirstLevel: {
anotherSecondLevel: ["one", "two"];
oneMoreSecondLevel: null;
};
}
type KeysOfUnion<T> = T extends T ? keyof T : never;
// ok
const a: RequireOnlyOneUnion<MenuItems[keyof typeof x], KeysOfUnion<MenuItems[keyof typeof x]>> = {
anotherSecondLevel: ["one", "two"],
};
// error
const b: RequireOnlyOneUnion<MenuItems[keyof typeof x], KeysOfUnion<MenuItems[keyof typeof x]>> = {
secondLevel: ["one", "two"],
anotherSecondLevel: ["one", "two"],
};
我不知道如何更改RequireOnlyOne
类型,但我知道如何创建新类型。
export interface MenuItems {
firstLevel: {
secondLevel: ['one', 'two']
};
anotherFirstLevel: {
anotherSecondLevel: ['one', 'two']
oneMoreSecondLevel: null
};
}
type Primitives = string | number | boolean | null | undefined | bigint | symbol
type UnionKeys<T> = T extends T ? keyof T : never;
// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
type Transform<Obj, Keys extends keyof Obj = keyof Obj, Result = never> =
StrictUnion<
Keys extends string ? { // #1
[Key in Keys]:
Key extends keyof Obj
? (Obj[Key] extends Primitives
? Obj[Key]
: (Obj[Key] extends any[]
? Obj[Key]
: Transform<Obj[Key], keyof Obj[Key], Obj[Key]>)
)
: never
} : Result>
type CustomType = Transform<MenuItems>
const workingObject: CustomType = {
firstLevel: { // Just one property of the first level
secondLevel: ['one', 'two'] // Just one property of the second level
},
}
const errorObject: CustomType = {
firstLevel: {
secondLevel: ['one', 'two']
},
anotherFirstLevel: { // Should not work as I am including 2 properties for the first level
anotherSecondLevel: ['one', 'two']
},
}
const anotherErrorObject: CustomType= {
anotherFirstLevel: {
anotherSecondLevel: ['one', 'two'],
oneMoreSecondLevel: null // Should not work neither as I am including 2 properties for second level
},
}
Transform
- 是一种主要的实用程序类型。 递归迭代键。 1# Keys extends string
- 这一行确保Keys is distributet
。 这意味着该行之后的整个代码将应用于每个键。 请参阅文档了解更多信息。
我还添加了Obj[Key] extends any[]
- 因为你不想(我想)遍历数组键。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.