Typescript 泛型,正在使用的关键,但它也使用符号 | 进行计算数量 |字符串也是如此

标签 typescript typescript-typings typescript-generics typescript-declarations

让我们直接进入示例。

在下面的代码中,您可以看到使用了 keyof,但 typescript 仍然将 EVT 的类型计算为 string|number|string,即使 EVT 扩展 keyof ((typeof Events)[OBJ])

代码:(最后一行自动完成和类型检查工作正常)

export const Events = {
    // [event: string]: (...args: any) => void;
    account: {
        available: (status?:  'online'|'offline',foo?:'test',bar?:'test2') => {
            console.error('1');
        },
    }
}

export  class AMQP {
    static event<
        OBJ extends keyof typeof Events,
        EVT extends keyof ((typeof Events)[OBJ]),
        FUNC extends ((typeof Events)[OBJ][EVT])
        >(
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}

AMQP.event('account','available','online','test','test2');

Playground Link

问题(悬停在“FUN”上的红旗)

Type 'FUNC' does not satisfy the constraint '(...args: any) => any'.
  Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][EVT]' is not assignable to type '(...args: any) => any'.
    Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][keyof { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ]]' is not assignable to type '(...args: any) => any'.
      Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string] | { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][number] | { ...; }[OBJ][symbol]' is not assignable to type '(...args: any) => any'.
        Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string]' is not assignable to type '(...args: any) => any'.(2344)

更新:::

截至目前。按照 @Maciej Sikora 的建议,通过强制将 FUN 声明为函数修复错误来解决问题。

但这是另一个例子。无需强制声明即可正常工作的地方 唯一的区别是事件不深,所以它可以在那里工作,但当对象更深一点时就不行了,我仍然很想知道原因。

Playground Link

最佳答案

按条件类型缩小类型

我们可以通过明确地告诉 TS 我们只想要函数来解决这个问题,尽管目前只有看起来 TS 无法缩小它的范围。考虑:

type E = typeof Events // for readability

export  class AMQP {
    static event<
        OBJ extends keyof E,
        EVT extends keyof E[OBJ],
        FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ?  E[OBJ][EVT] : never
        >(
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}

要点是 - FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT]:从不。这意味着我们只允许在对象结构的第二层中选择函数。看来现在TS不再提示了。

这种解决方案的额外优点是,如果我们提供一个不是函数的字段,则无法使用该选择调用该函数。

为什么TS无法缩小类型

有一些原因可以部分解释它。考虑类似的例子:

const a = {
    a: 2
}
const f = <A extends typeof a, K extends keyof A>(a: A, k: K) => {
    return a[k] + 1; // error we cannot use +
}
f(a, 'a');

如果a里面只有数字值,我们就不能使用+,这是怎么回事。看起来好像是错误的?不,它是正确的,因为我们的函数表示我们允许扩展 typeof a 的类型,因此它与 a 不同,我们可以拥有具有属性 a 的对象 还有另一个属性b,例如 bool 值。考虑下面的代码可以编译:

const b = {
    a: 2,
    b: true,
}
f(b, 'b');

所以我可以使用结构上具有相同字段的对象 - a 但也有另一个具有不同类型的对象。出于同样的原因,您的代码无法缩小类型,因为我们可以引入另一个结构相同的对象,但具有另一个非函数属性。

在我们的示例中,我们的情况略有不同,因为我们引用的是一个单一对象。那么,尚不完全清楚为什么在某些嵌套级别上推理会失败。 TS 需要额外的帮助来缩小功能范围。为什么对我来说是未知的,看起来像是推理限制。

为事件引入静态类型

您应该考虑的第二个解决方案是静态类型化事件接口(interface)。考虑:

type Events = Record<string, Record<string, (...a: any[]) => any>>
export const events = {
    account: {
        available: (status?: 'online' | 'offline', foo: 'test', bar: 'test2') => {
            console.error('1');
        },
    }
};

export  class AMQP {
    static event<
        INSTANCE extends Events,
        OBJ extends keyof INSTANCE,
        EVT extends keyof INSTANCE[OBJ],
        FUNC extends INSTANCE[OBJ][EVT]
        >(
        inst: INSTANCE,
        obj: OBJ,
        event: EVT,
        ...args: Parameters<FUNC>
    ) {

    }
}
// pay attention below I pass events object explicitly as first argument
AMQP.event(events, 'account', 'available', 'online', 'test', 'test2' );

请注意,我在这里介绍了一些内容:

  • 事件类型
  • 额外的第一个参数是Events接口(interface)的实例对象

这种解决方案更加灵活,因为实现不绑定(bind)到一个对象,您可以通过 Events 接口(interface)传递任何结构正确的对象,也不需要条件类型。

关于Typescript 泛型,正在使用的关键,但它也使用符号 | 进行计算数量 |字符串也是如此,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59765734/

相关文章:

javascript - 如何将多个类属性设置为一个对象的属性

typescript - 如何合并接口(interface)的所有值

typescript - Typescript能够执行简单的功能组合吗?

typescript - 输入和验证未知形状的对象数组

jquery - 使用 typescript 和 webpack 扩展 jQuery

javascript - Javascript、CoffeeScript、TypeScript、ES5 和 ES6 之间的关系

jquery - Typescript 和 JQuery 编译错误 : Cannot find name '$'

angular - ionic 2 cordova,安装 cordova 插件类型

typescript - 如何在 typescript 中专门化泛型类的模板类型?

javascript - 将动态数据传递给子组件