为什么TypeScript同时有`void`和`undefined`?

103
在 TypeScript 中,您可以将函数注释为返回 void:
function fn1(): void {
  // OK
}

function fn2(): void {
  // Error
  return 3;
}

您还可以为函数添加注释,以返回undefined

function fn3(): undefined {
  // OK
  return;
}

function fn4(): undefined {
  // Error
  return 3;
}

看起来,如果你调用一个返回void的函数,你总是会得到undefined的值。但你不能写出这样的代码:

function fn5(): void {
}
let u: undefined = fn5(); // Error

为什么 void 不只是 undefined 的别名?它是否需要存在?

3个回答

179
在函数返回类型中,void有特殊的含义,并不是undefined的别名。把它看作后者是非常错误的。为什么呢? void的意图是函数的返回值将不会被观察到。这与将会是undefined是非常不同的。重要的是要有这个区别,以便您可以正确地描述像forEach这样的函数。让我们考虑一个独立版本的Array#forEach,在回调返回位置使用undefined而不是void
declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;

如果您尝试使用此函数:

let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));

您会收到一个错误信息:
“类型'数字'不能赋值给类型'未定义'。”
这是一个正确的错误提示 - 您说您想要一个返回值为undefined的函数,但实际上您提供了一个返回值为number的函数,因为这是Array#push返回的内容!
使用void可以保证forEach不使用返回值,因此可以使用返回任何值的回调函数来调用它。
declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
// OK
forEach([1, 2, 3], el => target.push(el));

为什么不直接使用any?如果您是实现forEach的人,那么您真的不希望这样做-浮动的any是一件非常危险的事情,很容易破坏类型检查。
这个问题的推论是,如果您有一些函数表达式,其返回类型为void您无法确定调用该函数的结果是否为undefined
再次说明,void不是undefined的别名,类型为void的表达式可能具有任何值,而不仅仅是undefined
在显式列出返回类型为void的函数中,TypeScript会阻止您“意外”返回一个值,即使这不会创建类型系统违规。这有助于捕获从重构中出现的错误。
// Old version
function fn(arr: number[]): void {
  const arr1 = arr.map(x => {
    return 3;
  });
}

// New version
function fn(arr: number[]): void {
  for (const x of arr) {
    // Oops, meant to do something else
    return 3;
  };
}

2
非常好的解释,谢谢!我还有一个问题:为什么TS允许将void函数的返回值赋值给变量,如果我们根本不应该信任void函数的返回值呢? function b(): void {}; const y = b() - André Willik Valenti
9
因此,这是否意味着可以选择返回一些值(比如一个数字)的回调应声明为返回 number | undefined 而不是 number | void - David Glasser
4
那么 voidunknown 有什么区别呢?我认为 unknown 类型是“任何输入,没有输出”的类型?- 这非常适合函数返回类型未被观察到的情况。 - paul23
4
很不幸,似乎 TypeScript 有时候 确实会将 void 视为 undefined,这可能会导致问题:https://gist.github.com/rkjnsn/0435efb3af33d74b06d337f0f2706224 - rkjnsn
2
考虑到这将编译为JS运行时,所有这些都感觉过于理论化和有点误导。 - Ben Steward
显示剩余7条评论

2

它们在语义上是不同的。

  • undefined 意味着它不存在。这是存在的否定。
  • void 意味着你无法判断它是否存在。这是存在的可区分性的否定。

进一步思考,下面是 readonly 接口和 functional 接口之间的区别。

interface RegularDictionary<K, V>
{
    get(key: K): V;
    set(key: K, value: V): void;
}

interface ReadonlyDictionary<K, V>
{
    get(key: K): V;
}

interface FunctionalDictionary<K, V>
{
    get(key: K): V;
    set: never;
}

ReadonlyDictionary中,我们不知道方法set是否存在。我们不应该期望数据结构永远不变,因为它可能是一个实现了ReadonlyDictionary但具有set方法的类的实例。

这就是为什么ReadonlyDictionary不能严格用于函数式编程的原因。


1

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