我正在try 创建一个输出类型受输入类型约束的函数. 下面是我想要做的一个简单的例子:

function dummy<T extends string | number>(input: T) : T {
  let result: T

  if (typeof input === 'string') {
    result = 'foo'
  } else {
    result = 1
  }

  return result
}

但是,我收到以下错误消息:

类型‘字符串’不可赋值给类型‘T’."字符串"是可赋值的 类型"T"的约束,但"T"可以用 约束‘字符串|数字’的不同子类型.

我不确定如何修复这个问题,我本以为类型T会在if语句的作用域内折叠为字符串或数字,但事实似乎并非如此.

请注意,在实际的函数中,每个if语句中都有错误判断和解析,这些都是在最终返回之前处理的,这意味着我不能只是从内部返回.

推荐答案

编译器正确地警告您,返回"foo"1并不能保证满足generic返回类型T.字符串和数字literal types存在,因此如果调用dummy(123),则T将被推断为123,并且返回值1不能赋给123.这可能会导致运行时错误,如下所示:

function dummy<T extends string | number>(input: T): T {
    let result: T
    if (typeof input === 'string') { result = 'foo' } else { result = 1 }
    return result
}

const oops = dummy("abc");
//     ^? const oops: "abc" 
const obj = { abc: 123, def: 456 };
const num = obj[oops];
//    ^? const num: number

num.toFixed() // no compiler error, but
// ? RUNTIME ERROR! num is undefined ?

在这里,编译器认为oops"abc"类型的,这意味着它允许索引到键为"abc"的对象,这会导致问题.

您可以通过几种方式修复您的类型,但编译器不能真正验证您的实现是否满足这些类型.


一种方法是将函数的返回类型从T更改为conditional type,如果是T extends string,则输出string,否则将输出number,这样即使Tstringnumber窄,它仍然是正确的类型:

declare function dummy<T extends string | number>(
  input: T
): T extends string ? string : number; 

const str = dummy("abc");
// const str: string 
const num = dummy(123);
// const num: number

现在,从呼叫方的Angular 来看,这是可行的.尽管如此,编译器仍然会抱怨实现,因为它不能遵循逻辑.有关更好地支持此功能的相关功能请求,请参见microsoft/TypeScript#33912.目前,您需要使用一些type assertions来取消警告:

function dummy<T extends string | number>(input: T): T extends string ? string : number {
    let result: T extends string ? string : number;
    if (typeof input === 'string') {
        result = 'foo' as typeof result // <-- assert
    } else {
        result = 1 as typeof result // <-- assert
    }
    return result
}

另一种方法是使用多个调用签名来overload个函数:

declare function dummy(input: string): string;
declare function dummy(input: number): number;

const str = dummy("abc");
// const str: string
const num = dummy(123);
// const num: number

同样,从呼叫方的Angular 来看,这也是可行的.该实现不会给您带来编译器错误,但这是一种错误的安全感,因为编译器实际上并没有验证正确的输出与正确的输入:

function dummy(input: string): string;
function dummy(input: number): number;
function dummy(input: string | number) {
    let result: string | number;
    if (typeof input === 'string') {
        result = 'foo' // okay, but result = 1 would also be "okay" here
    } else {
        result = 1 // okay, but result = "foo" would also be "okay" here
    }
    return result
}

您也可以使用这两种方法,使用单个通用重载调用签名:

// call signature is generic
function dummy<T extends string | number>(
    input: T
): T extends string ? string : number;

// implementation is not
function dummy(input: string | number) {
    let result: string | number;
    if (typeof input === 'string') {
        result = 'foo';
    } else {
        result = 1;
    }
    return result
}

与其他代码一样,这不是类型安全的(您可以将typeof input === 'string'改为typeof input !== 'string',编译器不会注意到),但它可能会更方便.

Playground link to code

Typescript相关问答推荐

返回同时具有固定键和变量键的对象的函数的返回类型

键集分页顺序/时间戳上的筛选器,精度不相同

使用axios在ngOnInit中初始化列表

如何重构长联合类型?

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

是否有可能避免此泛型函数体中的类型断言?

接受字符串或数字的排序函数

创建一个TypeScrip对象并基于来自输入对象的约束定义其类型

类型脚本中参数为`T`和`t|unfined`的重载函数

如何创建一个将嵌套类属性的点表示法生成为字符串文字的类型?

如何避免多个类型参数重复?

使用NextJS中间件重定向,特定页面除外

TypeScript是否不知道其他函数内部的类型判断?

如何将 MUI 主题对象的自定义属性与情感样式组件中的自定义props 一起使用?

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

如何使用动态生成的键和值定义对象的形状?

TS2532:对象可能未定义

如何从 Select 元素中删除选项

如何解决联合类型上的此表达式不可调用?