我正在尝试创建一个可以拥有的界面
export interface MenuItem {
title: string;
component?: any;
click?: any;
icon: string;
}
Run Code Online (Sandbox Code Playgroud)
component
或click
设置KPD*_*KPD 81
借助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]
Run Code Online (Sandbox Code Playgroud)
并且要求提供一个且仅提供一个的部分但非绝对的方式是:
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]
Run Code Online (Sandbox Code Playgroud)
这是一个TypeScript playground链接,显示两者都在运行.
需要注意的RequireOnlyOne
是,TypeScript并不总是在编译时知道运行时将存在的每个属性.所以显然RequireOnlyOne
无法做任何事情来防止它不知道的额外属性.我提供了一个例子,说明如何RequireOnlyOne
在游乐场链接的末尾错过任何东西.
使用以下示例快速概述其工作原理:
interface MenuItem {
title: string;
component?: number;
click?: number;
icon: string;
}
type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
Run Code Online (Sandbox Code Playgroud)
Pick<T, Exclude<keyof T, Keys>>
from 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]
Run Code Online (Sandbox Code Playgroud)
哪个成了
{
component: { component: number, click?: number },
click: { click: number, component?: number }
}['component' | 'click']
Run Code Online (Sandbox Code Playgroud)
最终变成了
{component: number, click?: number} | {click: number, component?: number}
Run Code Online (Sandbox Code Playgroud)上面的1和2的交集
{ title: string, icon: string}
&
({component: number, click?: number} | {click: number, component?: number})
Run Code Online (Sandbox Code Playgroud)
简化为
{ title: string, icon: string, component: number, click?: number}
| { title: string, icon: string, click: number, component?: number}
Run Code Online (Sandbox Code Playgroud)for*_*d04 52
有一个更简单的解决方案。无需依赖any
或复杂的条件类型 (见答案):
- 有没有办法要求设置组件或单击?(含
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: {} } // ?
Run Code Online (Sandbox Code Playgroud)
if ("click" in testOr) testOr.click // works
Run Code Online (Sandbox Code Playgroud)
- 有没有办法要求不能设置这两个属性?(独家
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 set
Run Code Online (Sandbox Code Playgroud)
基本上任一 component
或 click
可设定,其他的应该从不1被同时加入。TS可以做出区分联合类型出来的MenuItemXor
,它对应于XOR
。
XOR
对于MenuItemXor
已接受的答案,此条件是不可能的。
1 从技术上讲,prop?: never
解析为prop?: undefined
,尽管前者经常用于说明。
ssu*_*ube 46
不使用单个接口,因为类型没有条件逻辑并且不能相互依赖,但您可以通过拆分接口:
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;
Run Code Online (Sandbox Code Playgroud)
Jua*_*des 10
没有多个接口的替代方法是
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: "" }
];
Run Code Online (Sandbox Code Playgroud)
我在如何创建需要设置单个属性的Partial-like中问过类似的问题
上面的内容可以简化,但可能会或可能不容易阅读
export type MenuItem = {
title: string;
icon: string;
} & (
{component: any} | {click: string}
)
Run Code Online (Sandbox Code Playgroud)
请注意,这些都不阻止您同时添加两者,因为TypeScript确实允许使用AND / OR的对象具有额外的属性,请参见https://github.com/Microsoft/TypeScript/issues/15447
我最终做了:
export interface MenuItem {
title: string;
icon: string;
}
export interface MenuItemComponent extends MenuItem{
component: any;
}
export interface MenuItemClick extends MenuItem{
click: any;
}
Run Code Online (Sandbox Code Playgroud)
然后我用:
appMenuItems: Array<MenuItemComponent|MenuItemClick>;
Run Code Online (Sandbox Code Playgroud)
但是希望有一种方法可以使用单个界面对其进行建模。
我喜欢Pick
与包含所有属性的基本类型一起使用来建立这些类型的条件要求。
interface MenuItemProps {
title: string;
component: any;
click: any;
icon: string;
}
export type MenuItem =
Pick<MenuItemProps, "title" | "icon" | "component"> |
Pick<MenuItemProps, "title" | "icon" | "click">
Run Code Online (Sandbox Code Playgroud)
这既干净又灵活。您可以使您的需求变得任意复杂,断言诸如“需要所有属性,仅需要这两个属性,或仅需要这一个属性”等内容,同时保持声明简单且可读。
这是实现其中一个但不能同时实现两者的简单方法
type MenuItem = {
title: string;
component: any;
click?: never;
icon: string;
} | {
title: string;
component?: never;
click: any;
icon: string;
}
// good
const menuItemWithComponent: MenuItem = {
title: 'title',
component: "my component",
icon: "icon"
}
// good
const menuItemWithClick: MenuItem = {
title: 'title',
click: "my click",
icon: "icon"
}
// compile error
const menuItemWithBoth: MenuItem = {
title: 'title',
click: "my click",
component: "my click",
icon: "icon"
}
Run Code Online (Sandbox Code Playgroud)
我用这个:
type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>
Run Code Online (Sandbox Code Playgroud)
用法:
let a : RequireField<TypeA, "fieldA" | "fieldB">;
Run Code Online (Sandbox Code Playgroud)
这使得fieldA
与fieldB
要求。
归档时间: |
|
查看次数: |
21349 次 |
最近记录: |