[英]How to create a RequireOnlyOne nested type in TypeScript?
I've seen this code snippet a lot:我经常看到这个代码片段:
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]
Here you can find the question from where I took it. 在这里你可以找到我提出的问题。
It works, however, I have this structure:它有效,但是,我有这样的结构:
export interface MenuItems {
firstLevel: {
secondLevel: ['one', 'two']
};
anotherFirstLevel: {
anotherSecondLevel: ['one', 'two'],
oneMoreSecondLevel: null
};
}
I need to apply RequireOnlyOne
for the first and the second levels, but I can't figure what to change on the type RequireOnlyOne
so it works for each firstLevel keys but also with secondLevel keys.我需要为第一级和第二级应用RequireOnlyOne
,但我无法确定RequireOnlyOne
类型的更改内容,因此它适用于每个 firstLevel 键,但也适用于 secondLevel 键。 As it is right now I can select only one firstLevel but multiple secondLevels of that firstLevel.就像现在一样,我只能选择一个 firstLevel,但可以选择该 firstLevel 的多个 secondLevel。
I also tried to compose a new type with an object which key could be RequireOnlyOne<keyof MenuItems>
and a value that uses also RequireOnlyOne
for the values, but couldn't make it.我还尝试用一个对象组合一个新类型,该对象的键可以是RequireOnlyOne<keyof MenuItems>
和一个也使用RequireOnlyOne
作为值的值,但无法实现。
Example of what I want, calling the desired type as customType
:我想要的示例,将所需类型称为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
};
}
The type should throw an error if the object has more than one first level property, and/or more than one second level property.如果对象具有多个第一级属性和/或多个第二级属性,则该类型应引发错误。 With the proposed RequireOnlyOne
type I can achieve that but just for the first level, but I need the same effect for first and second level.使用建议的RequireOnlyOne
类型,我可以实现这一点,但仅适用于第一级,但我需要第一级和第二级的相同效果。
Any ideas?有任何想法吗?
IMO it's a quite complex question and I'd recommend looking for alternatives and refactor the code. IMO 这是一个非常复杂的问题,我建议寻找替代方案并重构代码。
Otherwise, you might find this helpful:否则,您可能会发现这很有帮助:
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"],
};
I don't know how to change RequireOnlyOne
type but I know how to create new type.我不知道如何更改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
- is a main utility type. Transform
- 是一种主要的实用程序类型。 Recursively iterates over keys.递归迭代键。 1# Keys extends string
- this line makes sure that that Keys is distributet
. 1# Keys extends string
- 这一行确保Keys is distributet
。 It means that whole code which goes after this line will be applied to each key.这意味着该行之后的整个代码将应用于每个键。 Please see docs for more info.请参阅文档了解更多信息。
I have also added Obj[Key] extends any[]
- because you don't want (I suppose) to iterate though arrays keys.我还添加了Obj[Key] extends any[]
- 因为你不想(我想)遍历数组键。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.