为具有条件类型的可选参数声明从属参数类型

时间:2018-07-24 00:05:42

标签: typescript conditional-types

我有一个带有三个参数的类方法声明:

declare class Application {
    PrintOut(OutputFileName?: string, PrintToFile?: boolean, StartPage?: number): void;
}

打印到文件时,必须传递输出文件名 ;当不打印到文件时,无法传递文件名 。这意味着OutputFileName参数的类型取决于PrintToFile参数,如下所示:

  • 如果PrintToFiletrue,则OutputFileName的类型为string而不是undefined,即不是可选的。
  • 否则(PrintToFilefalse / undefined,或者未通过),则OutputFileName必须是undefined

或者,使用重载:

declare class Application {
    PrintOut(OutputFileName: string, PrintToFile: true, StartPage?: number): void;
    PrintOut(OutputFileName?: undefined, PrintToFile?: false, StartPage?: number): void;
}

以下呼叫有效:

x.PrintOut('abcd', true);
x.PrintOut(undefined, false);
x.PrintOut(undefined, undefined);
x.PrintOut();

,以下调用无效:

x.PrintOut(undefined, true);     // first parameter must be a string
x.PrintOut('abcd', false);       // cannot pass a filename if not printing to a file
x.PrintOut('abcd', undefined);  // (same as above)
x.PrintOut('abcd');             // (same as above)

Playground 1

如何使用条件类型复制上述重载的功能?

如果只有两个参数,我会坚持使用重载。但这只是MCVE。 actual method具有19个参数,其中多个参数取决于3个不同参数中每个参数的特定值;并将类似的方法应用于5种不同的对象类型。


使用此:

type IfPrintToFile<T, U> =
    T extends true ? U :
    undefined;

declare class Application {
    PrintOut<T extends boolean>(OutputFileName?: IfPrintToFile<T, string>, PrintToFile?: T, StartPage?: number): void;
}

不能阻止这些情况:

x.PrintOut(undefined, true);
x.PrintOut('abcd', undefined);
x.PrintOut('abcd');

Playground 1

在将方法的约束定义为T extends boolean | undefined时仍然不会阻止这些约束:

x.PrintOut(undefined, true);
x.PrintOut('abcd');

Playground 1


根据需要定义参数:

type IfPrintToFile<T, U> =
    T extends true ? U :
    undefined;

declare class Application {
    PrintOut<T extends boolean | undefined>(OutputFileName: IfPrintToFile<T, string>, PrintToFile: T, StartPage?: number): void;
}

阻止所有无效的呼叫,但也阻止以下有效的呼叫:

x.PrintOut();

Playground 1


1。看来选项设置没有存储在Playground URL中,并且每次都需要手动设置。

2 个答案:

答案 0 :(得分:2)

您现在可以使用rest参数来执行此操作(自jcalz在2018年回答以来,情况似乎有所改善!):

declare class Application {
    PrintOut<T extends boolean | undefined>(
        ...args: T extends true ? [
            OutputFileName: string,
            PrintToFile: T,
            StartPage?: number,
        ] : [
            OutputFileName?: undefined,
            PrintToFile?: T,
            StartPage?: number,
        ]
    ): void;
}

const x = new Application();

// valid calls
x.PrintOut('abcd', true);
x.PrintOut(undefined, false);
x.PrintOut(undefined, undefined);
x.PrintOut();

// invalid calls
x.PrintOut(undefined, true);
x.PrintOut('abcd', false);
x.PrintOut('abcd', undefined);
x.PrintOut('abcd');

这将精确地产生预期的错误并允许所有有效形式。参见playground

答案 1 :(得分:1)

我认为这可能无法完成。


据我所知,不能在函数参数(或对象属性)的类型内以编程方式打开或关闭可选函数参数(和对象属性)。也就是说,无法创建使它们等效的Optional<>类型别名

type FuncA = (x?: string) => void;
type FuncB = (x: Optional<string>) => void;

或这些等价物

type ObjA = {x?: string};
type ObjB = {x: Optional<string>};

有一个open issue,但我认为没有任何进展。因此,您想做的事可能就在那里受阻。


的确,从TS3.0开始,您将能够use tuple types in rest/spread positions,并且有一种方法可以处理一些可选参数:

  

元组类型中的可选元素

     

元组类型现在允许在元素类型上使用?后缀以指示该元素是可选的:

let t: [number, string?, boolean?];
t = [42, "hello", true];
t = [42, "hello"];
t = [42];
     

元组类型允许元素省略,只要该元素的类型具有后缀?,并且其右侧的所有元素也具有?修饰符。

     

当为其余参数推断出元组类型时,源中的可选参数将成为推断出的类型中的可选元组元素。

因此您可能很想使用它来创建可能的参数元组的联合,如下所示:

type PrintOutParams = [string, true, number?] | [undefined?, false?, number?]

但是您将不能在rest参数中使用它:

PrintOut(...args: PrintOutParams): void;
// error:  A rest parameter must be of an array type.

因为a union of array types is not considered to be an array type itself类型检查其余参数。至少从2015年起,此问题被认为是“设计使然”,不需要它的原因是……嗯,see for yourself

  

我们也许可以将规则从“ rest参数必须是数组类型”更改为“ rest参数必须是其中所有成分都是数组类型的数组类型或联合类型”,但这似乎不值得为之增加复杂性,因为添加重载来阐明预期的行为很容易。

这是真的……过载确实会为您提供所需的行为。您是否考虑过使用重载?

哦,对。


抱歉,我没有更好的消息。希望无论如何都会有所帮助;祝你好运!