我正在尝试创造一个界面

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}

是否有方法要求组件或单击进行设置 是否有一种方法要求两个属性都不能设置?


当前回答

有一个更简单的解决方案。不需要依赖任何 复杂条件类型(见答案):

是否有方法要求组件或单击进行设置?(包容或)

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: {} } // ✔

联合类型(|)对应于包含或。它与非条件属性相交。

使用in操作符将值缩小到其中一个组成部分:

if ("click" in testOr) testOr.click // works 

是否有一种方法要求两个属性都不能设置?(异或或除外)

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

基本上任何一个组件或点击都可以设置,另一个永远不要同时添加。TS可以从MenuItemXor中生成一个有区分的联合类型,它对应于XOR。

对于已接受的答案,MenuItemXor的此异或条件是不可能的。


操场上

从技术上讲,道具?:从来没有决心去支持?:未定义,尽管former常用于说明。

其他回答

没有多个接口的替代方案是

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: "" }
];

我在如何创建一个需要设置单个属性的Partial-like中问过类似的问题

上面的内容可以简化,但它可能更容易阅读,也可能不容易

export type MenuItem = {
  title: string;
  icon: string;
} & (
 {component: any} | {click: string}
)

注意,这些都不会阻止你同时添加两者,因为TypeScript确实允许在使用AND/OR的对象上添加额外的属性,请参阅https://github.com/Microsoft/TypeScript/issues/15447

我用这个:

type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>

用法:

let a : RequireField<TypeA, "fieldA" | "fieldB">;

这使得fieldA和fieldB是必需的。

这里有一个简单的方法来实现其中一个,但不是两个

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"
}

不是用一个接口,因为类型没有条件逻辑,不能相互依赖,但你可以通过分离接口:

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;

还有另一个解决方案:

type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;

type MenuItem2 = RequiredKeys<MenuItem, "component" | "click">;