说我有这种类型:
export interface Opts {
paths?: string | Array<string>,
path?: string | Array<string>
}
我想告诉用户它们必须通过路径或路径,但是没有必要同时通过两个路径。现在的问题是它可以编译:
export const foo = (o: Opts) => {};
foo({});
有人知道允许2个或更多可选参数,但TS至少需要1个参数吗?
答案 0 :(得分:14)
您可以使用
export type Opts = { path: string | Array<string> } | { paths: string | Array<string> }
为提高可读性,您可以编写:
type StringOrArray = string | Array<string>;
type PathOpts = { path : StringOrArray };
type PathsOpts = { paths: StringOrArray };
export type Opts = PathOpts | PathsOpts;
答案 1 :(得分:6)
如果您已经定义了该接口并希望避免重复声明,则可以选择创建一个条件类型,该条件类型接受一个类型并返回一个并集,其中并集中的每个类型都包含一个字段(以及一条记录)其他字段的never
值中,拒绝要指定的任何其他字段)
export interface Opts {
paths?: string | Array<string>,
path?: string | Array<string>
}
type EitherField<T, TKey extends keyof T = keyof T> =
TKey extends keyof T ? { [P in TKey]-?:T[TKey] } & Partial<Record<Exclude<keyof T, TKey>, never>>: never
export const foo = (o: EitherField<Opts>) => {};
foo({ path : '' });
foo({ paths: '' });
foo({ path : '', paths:'' }); // error
foo({}) // error
修改
这里使用的魔法类型的一些细节。我们将使用distributive property of conditional types来迭代T
类型的所有键。分配属性需要一个额外的类型参数才能工作,为此我们引入了TKey
,但由于要获取类型为T
的所有键,因此我们还提供了所有键的默认值。
因此,我们要做的实际上是获取原始类型的每个键,并创建一个仅包含该键的新映射类型。结果将是包含单个键的所有映射类型的并集。映射的类型将删除属性的可选性(-?
,描述为here),并且该属性的类型与T
中的原始属性相同(T[TKey]
)。
最后需要说明的部分是Partial<Record<Exclude<keyof T, TKey>, never>>
。由于对对象文字进行多余属性检查的工作方式,我们可以在分配给它的对象键中指定联合的任何字段。那是对于像{ path: string | Array<string> } | { paths: string | Array<string> }
这样的联合,我们可以为这个对象字面量{ path: "", paths: ""}
指定不幸的是。解决方案是要求,如果给定联合成员的对象文字中存在T
的任何其他属性(而不是TKey
,因此我们得到Exclude<keyof T, TKey>
),则它们应为{ {1}}(因此我们得到never
)。但是我们不想为所有成员显式指定Record<Exclude<keyof T, TKey>, never>>
,所以这就是我们never
以前的记录的原因。
答案 2 :(得分:3)
这有效。
它接受通用类型T
,在您的情况下为string
。
通用类型OneOrMore
定义T
中的1个或T
的数组。
您的通用输入对象类型Opts
是具有path
的键OneOrMore<T>
或paths
的键OneOrMore<T>
的对象。尽管不是很必要,但我明确指出唯一的其他选择是永远不会接受的。
type OneOrMore<T> = T | T[];
export type Opts<T> = { path: OneOrMore<T> } | { paths: OneOrMore<T> } | never;
export const foo = (o: Opts<string>) => {};
foo({});
{}
答案 3 :(得分:0)
您基本上是在寻找排他联合类型。
它已经proposed,但不幸的是,最终它被拒绝了。
我发现这里提出的解决方案不符合我的喜好,主要是因为我不喜欢花哨和复杂的类型。
您尝试过使用function overloading吗?
我处于类似的情况,对我来说,这就是解决方案。
interface Option1 {
paths: string | string[];
}
interface Option2 {
path: string | string[];
}
function foo(o: Option1): void;
function foo(o: Option2): void;
function foo(o: any): any {}
foo({ path: './' });
foo({ paths: '../' });
// The folling two lines gives an error: No overload matches this call.
foo({ paths: './', path: '../' });
foo({})
使用arrow function
时,与上述相同的代码将是:
interface Option1 {
paths: string | string[];
}
interface Option2 {
path: string | string[];
}
interface fooOverload {
(o: Option1): void;
(o: Option2): void;
}
const foo: fooOverload = (o: any) => {};
foo({ path: '2' });
foo({ paths: '2' });
// The following two lines gives an error: No overload matches this call.
foo({ paths: '', path: 'so' });
foo({});
希望这对您有所帮助!