我有以下hash个函数,当传递encoding个参数时,它们返回string.否则,它将返回Buffer

import { createHash, BinaryToTextEncoding } from "crypto";

const hash: {
  (data: string | Buffer): Buffer;
  (data: string | Buffer, encoding: BinaryToTextEncoding): string;

} = (data: string | Buffer, encoding?: BinaryToTextEncoding) => {
  const hash = createHash("sha256");

  hash.update(data);

  if (encoding) {
    // Hash.digest(encoding: BinaryToTextEncoding): string (+1 overload)
    return hash.digest(encoding) as any;
  }

  // Hash.digest(): Buffer (+1 overload)
  return hash.digest();
};

上述功能运行正常,

const hashed = hash("hello", "hex");  // const hashed: string

// or
const hashed = hash("hello");  // const hashed: Buffer

但是,我不清楚的是,我必须定义as any,而hash.digest(encoding)返回string

if (encoding) {
  // Hash.digest(encoding: BinaryToTextEncoding): string (+1 overload)
  return hash.digest(encoding) as any;
}

TypeScript将给出一个没有上述as any的错误:

Type '(data: string | Buffer, encoding?: BinaryToTextEncoding) => string | Buffer' is not assignable to type '{ (data: string | Buffer): Buffer; (data: string | Buffer, encoding: BinaryToTextEncoding): string; }'.
  Type 'string | Buffer' is not assignable to type 'Buffer'.
    Type 'string' is not assignable to type 'Buffer'.ts(2322)
const hash: {
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
}

你有什么 idea 吗?

推荐答案

具有多个调用签名的overloaded function的实现是在打字脚本中正确地键入never.编写重载的标准方法是使用类似于function statement

function hash(data: string | Buffer): Buffer;
function hash(data: string | Buffer, encoding: BinaryToTextEncoding): string;
function hash(data: string | Buffer, encoding?: BinaryToTextEncoding) {
    const hash = createHash("sha256");
    hash.update(data);
    if (encoding) {
        return hash.digest(encoding); // okay
    }
    return hash.digest(); // okay
};

你不需要写type assertion,就像as any一样.这在编译时没有错误,但这只是因为编译器只有loosely次根据调用签名判断实现.只要您返回类似于"a string or a Buffer"的内容,编译器就会接受它,即使它与encoding参数无关:

function badHash(data: string | Buffer): Buffer;
function badHash(data: string | Buffer, encoding: BinaryToTextEncoding): string;
function badHash(data: string | Buffer, encoding?: BinaryToTextEncoding) {
    return data; // also okay, even though this is not safe
};

你可以在microsoft/TypeScript#13235上读到这篇文章.对于编译器来说,要多次分析函数实现以确保分别满足每个调用签名,将会太复杂.因此,对于函数语句,它的错误是允许的,导致unsoundness.


另一方面,当你有一个像an arrow function这样的expression函数时,你会遇到相反的问题.编译器仍然不会 for each 调用签名分析一次实现;相反,它也会判断实现strictly,只有在返回类型适用于all调用签名时才接受它.对于函数表达式,它有限制方面的错误,导致了incompleteness.

因此,除非您的函数以某种方式返回string and a Buffer(例如,intersection type string & Buffer),否则它会报告:

const hash: { // error! string | Buffer not assignable to Buffer
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
} = (data: string | Buffer, encoding?: BinaryToTextEncoding) => {
    const hash = createHash("sha256");
    hash.update(data);
    if (encoding) {
        return hash.digest(encoding);
    }
    return hash.digest();
};

你可以在microsoft/TypeScript#47669岁时读到大约this期.建议以与重载函数语句相同的宽松方式判断重载函数表达式.但就目前而言,它不是语言的一部分,您需要一些类似类型断言或其他类型安全松动的东西.

如果您断言返回的每个值都是交集string & Buffer,那么它将不会出错地进行编译(即使基本上不可能同时是stringBuffer):

const hash: {
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
} = (data: string | Buffer, encoding?: BinaryToTextEncoding) => {
    const hash = createHash("sha256");
    hash.update(data);
    if (encoding) {
        return hash.digest(encoding) as (string & Buffer); // okay
    }
    return hash.digest() as (string & Buffer); // okay
};

或者,您可以使用单个as any断言,由于the intentionally unsafe any type的"传染性"性质,它会导致整个返回类型为any,这与所有内容都兼容,包括stringBuffer:

const hash: {
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
} = (data: string | Buffer, encoding?: BinaryToTextEncoding) => {
    const hash = createHash("sha256");
    hash.update(data);
    if (encoding) {
        return hash.digest(encoding);
    }
    return hash.digest() as any; // either return statement will do
};

或者,您可以在不使用断言的情况下仅将annotate the return value视为any(这只是因为any是不安全的,并且允许从几乎任何其他类型都可以赋值给and):

const hash: {
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
} = (data: string | Buffer, encoding?: BinaryToTextEncoding): any => { // here
    const hash = createHash("sha256");
    hash.update(data);
    if (encoding) {
        return hash.digest(encoding);
    }
    return hash.digest()
};

这些方法中的任何一种都将在编译器停止抱怨的意义上"起作用".但没有一个是安全的.事实上,使用any会使您更容易搞砸实现,而不会希望它被捕获:

const hash: {
    (data: string | Buffer): Buffer;
    (data: string | Buffer, encoding: BinaryToTextEncoding): string;
} = (data: string | Buffer, encoding?: BinaryToTextEncoding): any => {
    return 123; // also acceptable because of any
};

同样,重载的函数实现从未真正得到正确判断.您可以在漏报和误报之间进行 Select ,前者是编译器无法捕获真正的错误,后者则是编译器抱怨并不真正存在的错误...修复这些假阳性只意味着你再次打开了假阴性的可能性.因此,采取哪种方法取决于你.

Playground link to code

Typescript相关问答推荐

React Hook中基于动态收件箱定义和断言回报类型

界面中这种类型的界面界面中的多态性

带有联合参数的静态大小数组

APP_INITIALIZER—TypeError:appInits不是函数

webpack错误:模块找不到.当使用我的其他库时,

为什么tsx不判断名称中有"—"的属性?'

类型脚本类型字段组合

如何在TypeScrip中使用嵌套对象中的类型映射?

如何按不能保证的功能过滤按键?

TypeScrip:逐个 case Select 退出noUncheck kedIndexedAccess

诡异的文字类型--S为物语的关键

Angular 15使用react 式表单在数组内部创建动态表单数组

是否可以针对联合类型验证类型属性名称?

使用TypeScrip的类型继承

S,为什么我的T扩展未定义的条件在属性的上下文中不起作用?

将接口映射到交叉口类型

如何在打字角react 式中补齐表格组

TypeScrip:使用Cypress测试包含插槽的Vue3组件

如何为对象数组中的每个条目推断不同的泛型

Typescript 编译错误:TS2339:类型string上不存在属性props