TypeScript中的可选前导参数

8
在将JavaScript库协议转换为TypeScript时,我遇到了使用前置可选参数的情况,而不是常规/尾随参数。
JavaScript中的一个方法:
db.task(function (context) {
    // executing task
});

这个任务有一个可选的名称,可以在前面注入:

db.task('myTaskName', function (context) {
    // executing task
});

这样做是为了使代码更易读,在任务名称位于结尾的情况下,将其置于前面看起来不正确/不直观。

在TypeScript中如何编写此类参数?

我知道我可以将两个参数都声明为可选项,但实际上并非如此,因为回调函数必须作为第一个或第二个参数之一提供。如果它使问题更简单,我们可以说 - 最后一个参数必须是回调函数。


1
@fireydude 在JavaScript中,所有函数参数都是可选的和无类型的,你基本上会有(nameOrCallback,callback),然后检查callback === undefined来确定如何处理nameOrCallback。我希望没有人写这样的代码。 :) - Aaron Beall
@Aaron 写这样的代码确实很丑陋,但从可用性的角度来看,正如我在例子中所解释的那样,这是有道理的。 - vitaly-t
@vitaly-t 使用起来很容易理解,但代价是API对于单个参数有多种不同的含义,取决于顺序。我个人认为这不是一个好的权衡,因为有很多其他方法可以使其易读。只是我的观点而已。 - Aaron Beall
被接受的答案有一些问题。请看我的回答。 - basarat
2个回答

13

被接受的回答使用了一些不太正规的方法:

正确的做法

这是一个例子:

type Cb = (context: any) => any;
function task(cb: Cb);
function task(name: string, cb: Cb);
function task(nameOrCb: string | Cb, cb?: Cb) {
    if (typeof nameOrCb === 'string') {
        const name = nameOrCb; // You can see that `name` has the inferred type `string`
        // do something
    }
    else {
        const cb = nameOrCb; // You can see that `cb` has inferred type `Cb`
        // do something
    }
}

// Tests
task((a) => null); // Ok
task('test', (a) => null) // Ok

// Type Safety
task((a, b) => null); // Error: function does not match type cb
task('test'); // Error: `cb` must be provided for this overload

谢谢。我已经更新了我的答案(并注明了您的贡献)。这确实是一个更好的方法。 - Igor

7
如果你在定义函数参数时,想要标记它们为可选的,可以使用?符号,并使用|来显示可能注入的各种类型。在函数内部,您需要查看传递给哪个函数的参数。 RequireJS 在其定义函数中执行此操作,并具有一个.d.ts(明确声明)文件,其中显示了这一点。
以下是一个方法/函数的示例:
// definition
function myDefinedMethod(callback: (someVar:any)=>any):void;
function myDefinedMethod(name: string, callBack: (someVar: any) => any): void;
function myDefinedMethod(nameOrCallback: string | ((someVar:any)=>any), callBack ?: (someVar: any) => any): void {
    var name = "";
    if (typeof nameOrCallback === 'string') {
        name = nameOrCallback;
    }
    else {
        callBack = nameOrCallback;
    }

    // both name and callback are now defined although name can be empty
    // do something
    console.log(name);
}
  • 这种方法使用方法重载来确保类型安全,这将防止调用者在转换时间(因为typescript未编译)只使用字符串调用方法。
  • 在第三个函数定义中,参数nameOrCallback可以是函数或参数的名称。如果它是一个字符串,那么callback就不能为未定义。

编辑

谢谢@basarat,我已根据您的反馈更新了我的答案。这确实是更好的结构,因为您正在确保调用者无法在没有提供预期强制性参数(如回调)的情况下执行您的方法。我以前确实使用了Function,但只是作为占位符,用于任何OP想要使用的函数定义,并不是最终的类型参数。为了澄清这一点,我已经更新了代码,并添加了一个内联回调定义,就像你的一样。再次感谢您的反馈。这确实通过确保类型安全和确保调用者只能按照预期方式调用该方法使代码更加完善。


2
个人而言,我会称呼 name 为它本来的名字:nameOrCallback。这很丑陋,但它只是暴露了这种模式有多么丑陋。 - Aaron Beall
@Aaron - 同意,这不仅不美观,而且有些脆弱。我根据你的反馈更新了代码示例。 - Igor
@Igor,这个答案有一些问题。它不符合TypeScript的惯用法。请看我的答案。 - basarat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接