在 TypeScript 中重载联合类型
Posted
技术标签:
【中文标题】在 TypeScript 中重载联合类型【英文标题】:Overloading union types in 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
替换为WebGLTexture
和Input2
替换为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 问题:
https://github.com/Microsoft/TypeScript/issues/27131 https://github.com/microsoft/TypeScript/issues/14107以及这些其他问题:
Typescript overloading functions with union types Typescript function overloads not working for the union type case但令我困扰的是使用现有类型(即 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
...
【问题讨论】:
【参考方案1】:Typescript 是结构性的,而不是名义上的。这意味着如果两种类型具有相同的形状,但名称不同,则它们被认为是相同的类型。不幸的是,WebGLTexture
和 WebGLBuffer
的形状似乎相同。
如果您在 VSCode 或 typescript playground 中 cmd+click(或 ctrl+click)这些类型中的任何一种,您可以看到它们是如何声明的。这产生了这两个声明:
interface WebGLTexture extends WebGLObject
interface WebGLBuffer extends WebGLObject
遗憾的是,这两种类型都被简单地声明为未更改的WebGLObject
s。 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 对象。
拥有多个函数实现的优势在于,使用重载我只需要编写一次主体。以上是关于在 TypeScript 中重载联合类型的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript系列教程16TypeScript 联合类型