带有可选参数的恒等函数

Identity function with optional parameter

我们如何更改此恒等函数以使其参数可选,以便在不带参数调用时 returns 未定义?

function f<T>(x: T): T {
    return x;
}

var a: number = f(1);
var b: null = f(null);
var c: undefined = f(undefined);

明显的方法没有通过 strictNullChecks:

function f<T>(x?: T): T {
    return x; // TS2322
}

添加类型断言似乎可以解决问题……

function f<T>(x?: T): T {
    return x as T;
}

var d: undefined = f();

…但实际上为不正确的代码在运行时失败打开了大门:

var e: number = f();
console.log(e.toExponential());

给类型参数一个默认值没有帮助:

function f<T = undefined>(x?: T): T {
    return x as T;
}

很难让编译器验证你的实现是安全的,所以我认为你在这里做的最好的事情是保证调用者不会错误地调用它,但是你使用 type assertion 或 "moral equivalent" 以抑制实施中的警告。

最简单的方法是将 f 实现为 overloaded function:

// call signatures
function f(): undefined;
function f<T>(x: T): T;
// impl signature
function f(x?: any) {
    return x;
}

此处支持两种调用签名:结果为 undefined 且没有泛型类型参数的 zero-argument 调用,或者结果为 one-argument 泛型调用与输入相同。这应该完全符合您的预期:

var a = f(1); // number
var b = f(null); // null 
var c = f(undefined); // undefined
var d = f(); // undefined
var e: number = f(); // error! undefined is not number

重载函数是我调用 "morally equivalent" 类型断言的函数,因为允许实现签名比任何调用签名更宽松。所以你必须小心谨慎地正确实现功能。

如果出于某种原因您不想重载,您可以使用 conditional types along with rest tuples 创建一个通用函数,该函数接受零个或一个参数,其 return 类型具体取决于参数列表:

function f<P extends readonly [any?]>(
  ...x: P
): P extends readonly [infer R] ? R : undefined {
  return x as any;
}

上面的 ae 示例的行为方式相同。但是你仍然在做类型断言(as any 在实现中)而且它更加复杂和丑陋。我看到的唯一区别是,如果你用 union-typed 参数传播来调用它,就像这样:

var hmm = f(...Math.random() < 0.5 ? ["hey"] : []) // different

这适用于条件 f 但不适用于重载的。不过我怀疑你是否需要它。

好的,希望对你有帮助;祝你好运!

Link to code