typescript - TypeScript 中的 "...args: never[]"与 "...args: unknown[]"

标签 typescript typing

这里是 TypeScript 新手。我试图为“具有 .toString() 方法的事物”定义一个接口(interface),其中 .toString() 可以接受任意数量的参数,任何类型。

以下代码是一个最小示例,并且在界面中使用 ...args: never[] 作为 toString() 的参数似乎可以正常工作。我的问题是为什么;有人可以向我解释一下在这种情况下未知从不之间有什么区别吗?

interface Stringable {
  // toString: (...args: any[]) => string; // works but type-unsafe
  // toString: (...args: unknown[]) => string; // causes num and object to error
  toString: (...args: never[]) => string; // seems to work
}

const num: Stringable = 4;
const word: Stringable = "hello";
const obj: Stringable = { toString: (text: string) => text + "!!" };

console.log(num.toString());
console.log(word.toString());
console.log(obj.toString("world"));

最佳答案

never 是您永远无法分配任何内容但始终可以分配的类型

let x: never = 1; // can't assign annything to
let o: string = x; // can assign from 

Playground Link

unknown 则相反,它是您始终可以分配给的类型,但您永远无法分配自

let x: unknown = 1; // can assign anything to
let o: string = x; // can't assign from 

Playground Link

在函数签名的上下文中 (...args: never[]) => string 将是一个永远不能用任何参数调用的签名(因为我们永远不能将任何东西分配给never 类型的参数)。这样做的结果是,我们可以将任何函数实现分配给这样的签名,因为如果我们传入 never 类型的参数,因为 never 是可分配类型,因此 never value 可分配给该参数的值。

interface Stringable {
  toString: (...args: never[]) => string; 
}

// Since this is not a error
const obj: Stringable = { toString: (text: string) => text.toUpperCase() + "!!" };

// This has to be a TS error
console.log(obj.toString(1));

Playground Link

一个不幸的后果是,虽然我们无法传入参数,但我们仍然可以调用不带参数的函数。这看起来像是类型系统中的一个不健全的地方。

interface Stringable {
  toString: (...args: never[]) => string; 
}

const obj: Stringable = { toString: (text: string) => text.toLowerCase() + "!!" };

console.log(obj.toString("X")); // ts error
console.log(obj.toString()); // callable, will throw a runtime error

Playground Link

(...args:unknown[]) => string 是一个函数签名,可以采用任何数字参数和任何值。这意味着实现必须能够处理传入的任何值。这意味着我们可以分配一个根本不处理其参数的函数(例如 String.toString(): string),但是我们无法为其任何参数分配需要特定类型的参数(例如 Number.toString(radix?: number | undefined): stringobj.toString: (text: string ) => 字符串)

直观上我们可以看到我们想要一个错误:

interface Stringable {
  toString: (...args: unknown[]) => string; 
}

// IF this is not an error
const obj: Stringable = { toString: (text: string) => text.toUpperCase() + "!!" };

//  this is a runtime error
console.log(obj.toString(1));

Playground Link

原则上我们不能创建一个可以安全保存以下所有签名的函数签名:Number.toString(radix?: number | undefined): stringobj.toString: (文本:字符串)=> 字符串String.toString():字符串。我们不能这样做,因为 Number.toStringobj.toString 期望将冲突的类型作为第一个参数。调用两者的唯一安全方法是使用满足两个签名的参数,即 stringnumber (即 string & number )是从不

我们唯一可以安全地表示没有参数的 toString:

interface Stringable {
  toString: () => string; 
}

const num: Stringable = 4;
const word: Stringable = "hello";
const obj: Stringable = { toString: (text?: string) => text + "!!" };

console.log(num.toString());
console.log(word.toString());
console.log(obj.toString());

Playground Link

关于typescript - TypeScript 中的 "...args: never[]"与 "...args: unknown[]",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75183307/

相关文章:

haskell - 在 Haskell 中定义逻辑运算符

javascript - 我如何注释我想要任何类型作为输入,但与 Flow 的输出类型相同?

javascript - 如何在 TypeScript 中导出 Ramda 柯里化(Currying)函数?

在定义返回接口(interface)的函数时,Typescript 的行为似乎不一致

RequireJS 模块的 TypeScript 定义返回而不导出

emacs - Emacs 中自动关闭大括号?

parsing - Haskell:读取一个数字(整数或 float )

typescript - 无法使用函数类型的联合重载函数

typescript - 在 Typescript 中使用自定义键展平对象

JavaScript 类型。试图了解它的幕后工作原理