typescript 打字稿接口需要存在两个属性之一
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40510611/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
typescript interface require one of two properties to exist
提问by Nix
I'm trying to create an interface that could have
我正在尝试创建一个可能有的界面
export interface MenuItem {
title: string;
component?: any;
click?: any;
icon: string;
}
- Is there a way to require
component
orclick
to be set - Is there a way to require that both properties can't be set?
- 有没有办法要求
component
或click
设置 - 有没有办法要求不能设置这两个属性?
采纳答案by ssube
Not with a single interface, since types have no conditional logic and can't depend on each other, but you can by splitting the interfaces:
不是使用单个接口,因为类型没有条件逻辑并且不能相互依赖,但是您可以通过拆分接口:
export interface BaseMenuItem {
title: string;
icon: string;
}
export interface ComponentMenuItem extends BaseMenuItem {
component: any;
}
export interface ClickMenuItem extends BaseMenuItem {
click: any;
}
export type MenuItem = ComponentMenuItem | ClickMenuItem;
回答by KPD
With the help of the Exclude
type which was added in TypeScript 2.8, a generalizable way to require at least one of a set of properties is provided is:
Exclude
在 TypeScript 2.8 中添加的类型的帮助下,提供了一种需要一组属性中的至少一个属性的通用方法是:
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
}[Keys]
And a partial but not absolute way to require that one and only one is provided is:
要求提供一个且仅提供一个的部分但不是绝对的方法是:
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 is a TypeScript playground link showing both in action.
这是一个 TypeScript 游乐场链接,显示了两者的运行情况。
The caveat with RequireOnlyOne
is that TypeScript doesn't always know at compile time every property that will exist at runtime. So obviously RequireOnlyOne
can't do anything to prevent extra properties it doesn't know about. I provided an example of how RequireOnlyOne
can miss things at the end of the playground link.
需要注意的RequireOnlyOne
是,TypeScript 在编译时并不总是知道运行时存在的每个属性。所以显然RequireOnlyOne
不能做任何事情来防止它不知道的额外属性。我提供了一个示例,说明如何RequireOnlyOne
在 Playground 链接的末尾遗漏一些东西。
A quick overview of how it works using the following example:
使用以下示例快速概述其工作原理:
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
Pick<T, Exclude<keyof T, Keys>>
fromRequireAtLeastOne
becomes{ title: string, icon: string}
, which are the unchanged properties of the keys not included in'click' | 'component'
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]
fromRequireAtLeastOne
becomes{ component: Required<{ component?: number }> & { click?: number }, click: Required<{ click?: number }> & { component?: number } }[Keys]
Which becomes
{ component: { component: number, click?: number }, click: { click: number, component?: number } }['component' | 'click']
Which finally becomes
{component: number, click?: number} | {click: number, component?: number}
The intersection of steps 1 and 2 above
{ title: string, icon: string} & ({component: number, click?: number} | {click: number, component?: number})
simplifies to
{ title: string, icon: string, component: number, click?: number} | { title: string, icon: string, click: number, component?: number}
Pick<T, Exclude<keyof T, Keys>>
fromRequireAtLeastOne
become{ title: string, icon: string}
,这是未包含在其中的键的未更改属性'click' | 'component'
{ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys]
从RequireAtLeastOne
变成{ component: Required<{ component?: number }> & { click?: number }, click: Required<{ click?: number }> & { component?: number } }[Keys]
哪个变成
{ component: { component: number, click?: number }, click: { click: number, component?: number } }['component' | 'click']
最终变成
{component: number, click?: number} | {click: number, component?: number}
上面第 1 步和第 2 步的交集
{ title: string, icon: string} & ({component: number, click?: number} | {click: number, component?: number})
简化为
{ title: string, icon: string, component: number, click?: number} | { title: string, icon: string, click: number, component?: number}
回答by Juan Mendes
An alternative without multiple interfaces is
没有多个接口的替代方案是
export type MenuItem = {
title: string;
component: any;
icon: string;
} | {
title: string;
click: any;
icon: string;
};
const item: MenuItem[] = [
{ title: "", icon: "", component: {} },
{ title: "", icon: "", click: "" },
// Shouldn't this error out because it's passing a property that is not defined
{ title: "", icon: "", click: "", component: {} },
// Does error out :)
{ title: "", icon: "" }
];
I've asked a similar question at How to create a Partial-like that requires a single property to be set
我在How to create a Partial-like that requires a single property to be set 上问了一个类似的问题
The above could be simplified, but it may or may not be easier to read
以上可以简化,但可能更容易阅读,也可能不会
export type MenuItem = {
title: string;
icon: string;
} & (
{component: any} | {click: string}
)
Note that none of these prevent you from adding both because TypeScript does allow extra properties on objects that use AND/OR See https://github.com/Microsoft/TypeScript/issues/15447
请注意,这些都不会阻止您添加两者,因为 TypeScript 确实允许使用 AND/OR 的对象的额外属性参见https://github.com/Microsoft/TypeScript/issues/15447
回答by ford04
There is no need to use complex conditional types. We can make it simpler:
不需要使用复杂的条件类型。我们可以让它更简单:
- Is there a way to require component or click to be set? (inclusive
OR
)
- 有没有办法要求设置组件或单击?(含
OR
)
type MenuItemOr = {
title: string;
icon: string;
} & ({ component: object } | { click: boolean })
// brackets are important here: "&" has precedence over "|"
let testOr: MenuItemOr;
testOr = { title: "t", icon: "i" } // error, none are set
testOr = { title: "t", icon: "i", component: {} } // ?
testOr = { title: "t", icon: "i", click: true } // ?
testOr = { title: "t", icon: "i", click: true, component: {} } // ?
A union type(|
) corresponds to inclusive OR
. It is intersectedwith the non-conditional properties.
- Is there a way to require that both properties can't be set? (Exclusive
OR
/XOR
)
- 有没有办法要求不能设置这两个属性?(独家
OR
/XOR
)
type MenuItemXor = {
title: string;
icon: string;
} & (
| { component: object; click?: never }
| { component?: never; click: boolean }
)
let testXor: MenuItemXor;
testXor = { title: "t", icon: "i" } // error, none are set
testXor = { title: "t", icon: "i", component: {} } // ?
testXor = { title: "t", icon: "i", click: true } // ?
testXor = { title: "t", icon: "i", click: true, component: {} } // error, both are set
Basically eithercomponent
orclick
can be set, the other should neverbe added at the same time. TS can make a discriminated union typeout of MenuItemXor
, which corresponds to XOR
.
基本上任一component
或click
可设定,其他的应该从不被同时加入。TS可以做出区分联合类型出来的MenuItemXor
,它对应于XOR
。
Note: This XOR
condition for MenuItemXor
is not possible with accepted answer.
注意:XOR
对于MenuItemXor
已接受的答案,此条件是不可能的。
回答by Nix
I ended up doing:
我最终做了:
export interface MenuItem {
title: string;
icon: string;
}
export interface MenuItemComponent extends MenuItem{
component: any;
}
export interface MenuItemClick extends MenuItem{
click: any;
}
Then I used:
然后我用:
appMenuItems: Array<MenuItemComponent|MenuItemClick>;
But was hoping there was a way to model it with a single interface.
但是希望有一种方法可以使用单个界面对其进行建模。
回答by Dane
I like using Pick
along with a base type that includes all properties to establish these kinds of conditional requirements.
我喜欢Pick
与包含所有属性的基类型一起使用来建立这些类型的条件要求。
interface MenuItemProps {
title: string;
component: any;
click: any;
icon: string;
}
export interface MenuItem =
Pick<MenuItemProps, "title" | "icon" | "component"> |
Pick<MenuItemProps, "title" | "icon" | "click">
This is clean and also flexible. You can get arbitrarily complex with your requirements, asserting things like "require either all the properties, just these two properties, or just this one property" and so on while keeping your declaration simple and readable.
这很干净,也很灵活。您可以对您的需求进行任意复杂的处理,断言诸如“需要所有属性,仅需要这两个属性,或仅需要这一个属性”等,同时保持您的声明简单易读。
回答by Mohamed Allal
To just extends upon the cool answers above! And for the people that land here while searching for a Partialversion with requiring capability! Here a snippet i made to take!
只是扩展上面很酷的答案!对于那些在寻找需要能力的部分版本时登陆这里的人!这是我制作的一个片段!
PartialReq
部分请求
You want to have a Partial of an interface, but in mean time require some of the fields! Here how it's done
你想要一个接口的一部分,但同时需要一些字段!这是如何完成的
export type PartialReq<T, Keys extends keyof T = keyof T> =
Pick<Partial<T>, Exclude<keyof T, Keys>>
& {
[K in Keys]: T[K]
};
Use example
使用示例
export interface CacheObj<SigType = any, ValType = any> {
cache: Map<SigType, ValType>,
insertionCallback: InsertionCallback<SigType, ValType> // I want this to be required
}
// ...
export class OneFlexibleCache<SigType = any, ValType = any> {
private _cacheObj: CacheObj<SigType, ValType>;
constructor(
cacheObj: PartialReq<CacheObj<SigType, ValType>, 'insertionCallback'> // <-- here
// i used it
) {
cacheObj = cacheObj || {};
this._cacheObj = {
// ...
// _______________ usage
this._caches.set(
cacheSignature,
new OneFlexibleCache<InsertionSigType, InsertionValType>({
insertionCallback // required need to be provided
})
);
Here you can see that it work perfectly
在这里你可以看到它完美地工作
If the required not provided
如果未提供所需
UPDATE: For the usage that i implied above here a better answer
更新:对于我上面暗示的用法,这里有一个更好的答案
I just went by the doc and found Omit.
我刚刚去看了文档,发现了Omit。
https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
https://www.typescriptlang.org/docs/handbook/utility-types.html#omitk
I came to add it. But before i do, I just seen this cool answer. It cover all:
我是来补充的。但在我这样做之前,我刚刚看到了这个很酷的答案。它涵盖了所有:
https://stackoverflow.com/a/48216010/7668448
https://stackoverflow.com/a/48216010/7668448
Just check it out! It show how to do it for all the different version of Typescript! And for the sake of not repeating ! Go and check!
看看吧!它展示了如何为所有不同版本的 Typescript 做到这一点!为了不重复!去检查一下!