我正在尝试创建一个可能有
的界面export interface MenuItem {
title: string;
component?: any;
click?: any;
icon: string;
}
component
或click
答案 0 :(得分:49)
借助TypeScript 2.8中添加的Exclude
类型,提供了一种需要至少一组属性的通用方法:
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]
要求提供一个且仅提供一个的部分但非绝对的方式是:
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.
RequireOnlyOne
的警告是,TypeScript在编译时并不总是知道运行时将存在的每个属性。显然RequireOnlyOne
无法做任何事情来阻止它不知道的额外属性。我提供了一个示例,说明RequireOnlyOne
如何在游乐场链接的末尾错过任何内容。
使用以下示例快速概述其工作原理:
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
Pick<T, Exclude<keyof T, Keys>>
的 RequireAtLeastOne
变为{ 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}
答案 1 :(得分:26)
无需使用complex conditional types。我们可以使其更简单:
- 是否可以要求设置组件或单击? (包括
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: {} } // ✔
union type(|
)对应于包含的OR
。它是intersected,具有非条件属性。
- 是否有一种方法不能要求同时设置两个属性? (独家
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
基本上 component
或 click
都可以设置,另一个never应该同时添加。 TS可以从MenuItemXor
中产生discriminated union type,这与XOR
相对应。
注意:accepted answer无法实现XOR
的这种MenuItemXor
条件。
答案 2 :(得分:25)
不使用单个接口,因为类型没有条件逻辑且不能相互依赖,但您可以通过拆分接口:
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;
答案 3 :(得分:5)
没有多个接口的替代方案是
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: "" }
];
我在How to create a Partial-like that requires a single property to be set
问了一个类似的问题以上内容可以简化,但可能更容易阅读
export type MenuItem = {
title: string;
icon: string;
} & (
{component: any} | {click: string}
)
请注意,这些都不会阻止您添加两者,因为TypeScript确实允许使用AND / OR的对象的额外属性请参阅https://github.com/Microsoft/TypeScript/issues/15447
答案 4 :(得分:2)
我使用这个:
type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>
用法:
let a : RequireField<TypeA, "fieldA" | "fieldB">;
这使得fieldA
和fieldB
是必需的。
答案 5 :(得分:1)
我最终做了:
export interface MenuItem {
title: string;
icon: string;
}
export interface MenuItemComponent extends MenuItem{
component: any;
}
export interface MenuItemClick extends MenuItem{
click: any;
}
然后我用了:
appMenuItems: Array<MenuItemComponent|MenuItemClick>;
但希望有一种方法可以用单一界面对其进行建模。
答案 6 :(得分:1)
我喜欢将Pick
与包含所有属性的基本类型一起使用来建立这些条件要求。
interface MenuItemProps {
title: string;
component: any;
click: any;
icon: string;
}
export interface MenuItem =
Pick<MenuItemProps, "title" | "icon" | "component"> |
Pick<MenuItemProps, "title" | "icon" | "click">
这既干净又灵活。您可以随心所欲地满足您的要求,断言诸如“要求所有属性,仅这两个属性或仅此一个属性”之类的事情,等等,同时保持声明的简单性和可读性。
答案 7 :(得分:0)
(回答“胡安·门德斯”更像是一个问题而不是答案)?
<i class="fa fa-exclamation-triangle"></i>
我的想法(intellij / webstorm)允许我声明类型而没有错误,但是抱怨如果引用组件或单击。我猜这是正确的,因为不能保证它在那里。 所以我必须像这样引用它
<i class="fa fa-exclamation-triangle" style="color:red"></i>
这似乎并不能阻止我这样做
export type MenuItemCommon = {
title: string;
icon: string;
}
export type Component = {
component: int;
}
export type Click = {
click: string;
}
export type MenuItem = MenuItemCommon & (Component | Click)
即同时指定两个属性
答案 8 :(得分:0)
仅对以上很酷的答案进行扩展!对于在这里搜索需要功能的 Partial 版本的人!这是我要摘录的摘录!
您希望具有接口的Partial,但同时需要一些字段!这是怎么做的
export type PartialReq<T, Keys extends keyof T = keyof T> =
Pick<Partial<T>, Exclude<keyof T, Keys>>
& {
[K in Keys]: T[K]
};
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
})
);
在这里您可以看到它运行完美
如果未提供要求
我刚浏览文档,发现忽略。
https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
我来添加它。但是在我这样做之前,我只是看到了这个很酷的答案。它涵盖了所有内容:
https://stackoverflow.com/a/48216010/7668448
只需检查一下!它展示了如何为所有不同版本的Typescript做到这一点!并且为了不重复!去检查!
答案 9 :(得分:0)
另一种解决方案:
type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
type MenuItem2 = RequiredKeys<MenuItem, "component" | "click">;
答案 10 :(得分:0)
这种方法结合了 never
和 Omit
。这样做的好处是它易于理解,并且如果您需要添加更多属性也易于更新。
interface Base {
title: string;
icon: string;
component?: never;
click?: never;
}
interface OnlyComponent {
component: any;
}
interface OnlyClick {
click: any;
}
export type MenuItem = (Omit<Base, 'component'> & OnlyComponent) | (Omit<Base, 'click'> & OnlyClick);
您可以使用 in
来缩小 MenuItem
的实例范围:
const item: MenuItem = {
title: 'A good title';
icon: 'fa-plus';
component: SomeComponent;
};
//...
if('component' in item) {
const Comp = item.component;
//...
}