【问题标题】:Overloading union types in TypeScript在 TypeScript 中重载联合类型
【发布时间】:2021-04-11 12:27:27
【问题描述】:

我想定义一个函数f,使得参数和返回类型是联合类型,但它们是绑定的,当您调用f(anInputType) 时,您会得到aCorrespondingOutputType。我尝试了以下方法,它似乎适用于 TypeScript 4.1.3。

class Input1 {
    type1Input: string = "I am type 1!";
}

class Input2 {
    type2Input: string = "I am type 2!";
}

interface Output1 {
    type1Output: string;
}

interface Output2 {
    type2Output: string;
}

export type Input = Input1 | Input2;
export type Output = Output1 | Output2;

function f(_input: Input1): Output1;
function f(_input: Input2): Output2;
function f(_input: Input): Output {
    return null as any as Output;
}

const input = new Input2();

const output = f(input);
output.type2Output // Compiles because the compiler can figure out that output is of type Output2

但是,在(我的)现实生活中,输入应该是 WebGL 对象。同样的事情,我将Input1 替换为WebGLTextureInput2 替换为WebGLBuffer,无法编译。

interface Output1 {
    type1Output: string;
}

interface Output2 {
    type2Output: string;
}

export type Output = Output1 | Output2;

function f(_input: WebGLTexture): Output1;
function f(_input: WebGLBuffer): Output2;
function f(_input: WebGLTexture | WebGLBuffer): Output {
    return null as any as Output;
}

const canvas = document.createElement("canvas")!;
const gl = canvas.getContext("webgl")!;
const input = gl.createBuffer()!;

const output = f(input);
output.type2Output // Does not compile because the compiler thinks that output is of type Output1

我错过了什么吗?

我正在阅读这些 TypeScript 问题:

以及这些其他问题:

但令我困扰的是使用现有类型( WebGL 对象)与自定义类的行为之间的差异。

其实我是采用Alex的想法

它看起来很冗长,但有了它我可以摆脱一个单一的实现。由于应用程序其他部分的组织方式,这对我来说有点失败。 某事,某处,会看起来很丑。但这现在很好!谢谢!

下面的真实代码。

export type WebGLResource =
  { texture: WebGLTexture } |
  { buffer: WebGLBuffer } |
  { program: WebGLProgram } |
  { renderbuffer: WebGLRenderbuffer } |
  { framebuffer: WebGLFramebuffer };
export type ResourceMeta = TextureMeta | BufferMeta | ProgramMeta | RenderbufferMeta | FramebufferMeta;

function getMeta(<...omitted params...> resource: { texture: WebGLTexture }, required: true): TextureMeta;
function getMeta(<...omitted params...> resource: { buffer: WebGLBuffer }, required: true): BufferMeta;
function getMeta(<...omitted params...> resource: { program: WebGLProgram }, required: true): ProgramMeta;
function getMeta(<...omitted params...> resource: { renderbuffer: WebGLRenderbuffer }, required: true): RenderbufferMeta;
function getMeta(<...omitted params...> resource: { framebuffer: WebGLFramebuffer }, required: true): FramebufferMeta;
function getMeta(<...omitted params...> resource: { texture: WebGLTexture }, required: false): TextureMeta | null;
function getMeta(<...omitted params...> resource: { texture: WebGLBuffer }, required: false): BufferMeta | null;
function getMeta(<...omitted params...> resource: { buffer: WebGLProgram }, required: false): ProgramMeta | null;
function getMeta(<...omitted params...> resource: { renderbuffer: WebGLRenderbuffer }, required: false): RenderbufferMeta | null;
function getMeta(<...omitted params...> resource: { framebuffer: WebGLFramebuffer }, required: false): FramebufferMeta | null;
function getMeta(<...omitted params...> resource: WebGLResource, required: boolean): ResourceMeta | null {
  ...
}

【问题讨论】:

    标签: typescript typescript-typings


    【解决方案1】:

    Typescript 是结构性的,而不是名义上的。这意味着如果两种类型具有相同的形状,但名称不同,则它们被认为是相同的类型。不幸的是,WebGLTextureWebGLBuffer 的形状似乎相同。

    如果您在 VSCode 或 typescript playground 中 cmd+click(或 ctrl+click)这些类型中的任何一种,您可以看到它们是如何声明的。这产生了这两个声明:

    interface WebGLTexture extends WebGLObject {}
    interface WebGLBuffer extends WebGLObject {}
    

    遗憾的是,这两种类型都被简单地声明为未更改的WebGLObjects。 Typescript 无法区分它们。因此,当您尝试触发第二个函数重载时,typescript 会注意到第一个重载匹配,并使用它。

    很难从这个人为的代码中找到更好的解决方案,但您可能会想要一种不同的方法。


    也许您可以改为接受一个带有键的对象,以明确您传入的内容?这样不同的重载并有不同的参数类型。

    function f(_input: { texture: WebGLTexture }): Output1;
    function f(_input: { buffer: WebGLBuffer }): Output2;
    function f(_input: { texture?: WebGLTexture, buffer?: WebGLBuffer }): Output {
        return null as any as Output;
    }
    
    const canvas = document.createElement("canvas")!;
    const gl = canvas.getContext("webgl")!;
    
    const buffer = gl.createBuffer()!;
    const texture = gl.createTexture()!;
    
    f({ texture }).type1Output; // works
    f({ buffer }).type2Output; // works
    
    

    Playground

    【讨论】:

    • 谢谢你,亚历克斯,很棒的答案。我正在研究的一大卖点是它几乎在所有地方都使用原生 WebGL 类型。我想在这里做的是,在很多方面,包装的替代品。尽管我不得不说,由于缩短了 { property } 语法,您的代码看起来非常好。有一天我肯定会用这个。对于这个项目,对我来说最好的选择可能是放弃重载并提供单独的函数(我只有 5 种类型要映射;纹理、缓冲区、帧缓冲区、渲染缓冲区和程序)。谢谢!
    • 是的,重载可能有点痛苦。如果使用得当,它们可以强大且富有表现力,但从 fBuffer(buffer)fTexture(texture) 开始也没有错。不那么棘手,更容易理解,代码通常是好的。乐意效劳。祝你好运!
    • 这个问题的解决方法似乎很晦涩。为什么有人会这样做?
    • > 这个问题的解决方法似乎很晦涩。是的,这不是很好,但我的问题是我有这个返回 WebGL 资源的类;我希望它们作为原始 WebGL 对象返回,而不是代理或包装或不透明或类似的东西。用户可以随时将这些对象传回,我需要访问一些每个对象的附加信息来处理这些请求。我暂时不得不忍受这个,但我今天学到的最有可能的结果是,我可能会重构代码以不返回原始 WebGL 对象。
    • 拥有多个函数实现的优势在于,使用重载我只需要编写一次主体。
    猜你喜欢
    • 2021-02-20
    • 2016-08-27
    • 2017-07-18
    • 2018-10-17
    • 1970-01-01
    • 2020-02-25
    • 2019-02-13
    • 2019-08-28
    • 1970-01-01
    相关资源
    最近更新 更多