TypeScript需要一个参数或另一个参数,但都不是

时间:2018-09-01 22:42:29

标签: typescript tsc typescript3.0

说我有这种类型:

export interface Opts {
  paths?: string | Array<string>,
  path?: string | Array<string>
}

我想告诉用户它们必须通过路径或路径,但是没有必要同时通过两个路径。现在的问题是它可以编译:

export const foo = (o: Opts) => {};
foo({});

有人知道允许2个或更多可选参数,但TS至少需要1个参数吗?

4 个答案:

答案 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({});

希望这对您有所帮助!