我有以下场景:

class Dispatcher<R extends (...args: any) => any, T = undefined> {
    fn: R
    extra: T
    constructor(fn: R, extra?: T) {
        this.fn = fn;
        this.extra = extra;
    }
}

const test = new Dispatcher(() => {
    return true;
});

// TS type hinting should know that extra is undefined
test.extra;

const test2 = new Dispatcher(() => {
    return true;
}, {
    graphUrl: "/foo/"
});

// TS type hinting should know that extra exists and has a property graphUrl
test2.extra.graphUrl

在本例中,我希望extra类成员根据是否传递给构造函数来推断.如果我传递布尔值,它应该是布尔值,如果我传递字符串,它应该是字符串,如果我传递未定义,它应该是未定义的.现在推理工作正常,extra个类型正确,但当我分配this.extra = extra时,TS在构造函数代码中抛出了一个错误.严格地说,这个错误是有意义的,因为我将一个可以未定义的类型分配给一个不能定义的类型.问题是,如果我将成员定义更新为extra?: T,那么推理就会中断,因为现在它认为extra类似于string | undefined,这是不正确的.问题是额外的变量实际上不是"可选的",而是传递的值,该值可能未定义...这与可选的不同.

Type 'T | undefined' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to 'T | undefined'.

如果我修复了构造函数代码中的错误,那么类型推断就会停止工作,我得到的结果是string | undefined,这是不准确的.我们之所以 Select know类型,是因为它被传递给了构造函数.

Playground link

推荐答案

不幸的是,泛型类型并不总是推断出来的.有时它们是明确的.因此,typescript可以防止出现如下情况:

new Dispatcher<() => boolean, string>(() => true)

现在,根据类的类型,这是实例化类的有效方法.但在内部,当extra应该是string时,它将被分配为undefined.


我不完全确定如何在没有类型断言的情况下修复这个问题,但至少可以确保进行类型断言是安全的.

如果T未定义,我们需要做的是使参数可选.您可以通过将函数参数设置为解析为元组的条件类型来实现这一点.

class Dispatcher<R extends (...args: any) => any, T = undefined> {
    fn: R
    extra: T
    constructor(...args: T extends undefined ? [fn: R] : [fn: R, extra: T]) {
        const [fn, extra] = args
        this.fn = fn;
        this.extra = extra as T; // as T is still needed, sadly.
    }
}

现在,如果T未定义,则参数元组中缺少参数.否则,extra是必需的,并且需要为T型.

上述 case 没有出现错误,现在是一个错误:

new Dispatcher<() => boolean, string>(() => true) // Expected 2 arguments, but got 1.(2554)

这里因为Tstring,所以需要第二个参数.

但这些仍能如期发挥作用:

new Dispatcher<() => boolean>(() => true) // fine
new Dispatcher<() => boolean, undefined>(() => true) // fine
new Dispatcher<() => boolean, string>(() => true, 'foobar') // fine

Typescript仍然对extra可以安全分配感到困惑,我真的不知道如何解决这个问题.但至少现在我认为,不可能将不受支持的类型转换为extra,因此as T应该是安全的.

Playground

不过,可能还有更好的解决方案.

Typescript相关问答推荐

为什么当类类型与方法参数类型不兼容时,该函数可以接受类类型

类型脚本不会推断键的值类型

在将对象从一个对象转换成另一个对象时,可以缩小对象的键吗?

在配置对象上映射时,类型脚本不正确地推断函数参数

TypeScript:在作为参数传递给另一个函数的记录中对函数的参数实现类型约束

我想创建一个只需要一个未定义属性的打字脚本类型

等待用户在Angular 函数中输入

为什么我的一个合成函数不能推断类型?

为什么Typescript不能推断类型?

推断从其他类型派生的类型

刷新页面时,TypeScrip Redux丢失状态

FatalError:Error TS6046:';--模块分辨率';选项的参数必须是:'; node ';,'; node 16';,'; node

如何从输入中删除值0

缩小对象具有某一类型的任意字段的范围

TypeScrip-根据一个参数值验证另一个参数值

将布尔值附加到每个对象特性

如何键入派生类的实例方法,以便它调用另一个实例方法?

如何将通用接口的键正确设置为接口的键

React Context TypeScript错误:属性';firstName';在类型';iCards|iSearch';

为什么类方法参数可以比接口参数窄