是否可以动态修改 TypeScript 类型别名?

huangapple go评论59阅读模式
英文:

Is it possible to modify a Typescript type alias dynamically?

问题

我正在开发一个 Angular 库,它将作为一个 API 客户端。我遇到的问题是,一些使用该库的应用程序正在使用 HttpInterceptor 来自动将日期字符串转换为 JavaScript 日期对象,而其他一些应用程序则只使用响应中传递的字符串值。

我目前将这些属性类型定义为 string | Date,但我真的希望根据消费库的应用程序来缩小联合类型,使其要么是 string 要么是 Date

我首先尝试声明一个类型别名 type DateType = string | Date,然后希望能够在消费应用程序中使用 declare global 块来覆盖它,但这并不起作用。

我还考虑过定义一个接口 interface DateType extends String,然后在消费应用程序中覆盖它,但我放弃了这条路,因为我不希望消费应用程序必须管理 String 对象和基本类型之间的转换。

英文:

I am working on an Angular library that will serve as an API client. The issue I'm running into is that some of the consuming applications are using an HttpInterceptor to automatically convert date strings into Javascript Date objects while others just use the string value passed in the response.

I currently have these properties typed as string | Date but I would really like a way to have the union type narrowed to either string or Date based on which application is consuming the library.

I first tried declaring a type alias type DateType = string | Date and hoped I could override it using a declare global block in the consuming application but that didn't work.

I also considered defining an interface interface DateType extends String and then overriding that in the consuming application but I abandoned that route as I don't want to have the consuming applications have to manage the conversion between the String object and primitive.

答案1

得分: 1

为什么不像这样用一个泛型参数来定义你的函数?

    declare function callApi<T extends Date | string>(): T;
    
    const date =  callApi<Date>();
    const str = callApi<string>();

[TypeScript playground][1]
英文:

Why not just type your function with a generic parameter like this?

declare function callApi&lt;T extends Date | string&gt;(): T;

const date =  callApi&lt;Date&gt;();
const str = callApi&lt;string&gt;();

TypeScript playground

答案2

得分: 0

TypeScript不支持对现有类型进行任意修改。特别是你不能修改类型别名。但是TypeScript通过声明合并支持对某些类型进行修改,你可以使用(或滥用)它来获得你所期望的行为。

基本上,你可以“重新打开”接口声明并添加属性。如果库的类型定义如下所示:

interface DateTypeConfig {
    [x: string]: string | Date;
}
type DateType = DateTypeConfig["type"];

declare function acceptDate(date: DateType): void;
declare function produceDate(): DateType;
declare function modifyDate(date: DateType): DateType;

那么DateType是对DateTypeConfig接口的type属性的索引访问。现在DateTypeConfig接口目前没有具体的type属性,但它有一个类型为string | Datestring 索引签名,因此默认情况下,DateTypestring | Date

因此,默认情况下,消费应用程序将看到这个联合类型。

const d = produceDate();
// const d: string | Date
const m = modifyDate(d);
// const m: string | Date
acceptDate(m);

但是消费应用程序可以重新打开DateTypeConfig接口并添加一个明确的type属性,其类型是string | Date的某个子类型。 (如果这是在模块中,或者你的库在一个模块中,你可能需要使用global扩充module扩充来获取正确的命名空间。)

例如:

interface DateTypeConfig {
    type: Date;
}

然后自动地,DateType类型别名变成了Date,而不是string | Date

const d = produceDate();
// const d: Date
const m = modifyDate(d);
// const m: Date
acceptDate(m);

或者应用程序可以这样做:

interface DateTypeConfig {
    type: string;
}

然后DateType将是string

const d = produceDate();
// const d: string
const m = modifyDate(d);
// const m: string
acceptDate(m);

Playground链接到代码

英文:

TypeScript doesn't support arbitrary modifications to existing types. In particular you can't modify type aliases. But TypeScript does support certain type modifications through declaration merging, which you can use (or abuse) to get the behavior you're looking for.


Essentially you can "re-open" an interface declaration and add properties to it. If the library's types are defined like this:

interface DateTypeConfig {
    [x: string]: string | Date
}
type DateType = DateTypeConfig[&quot;type&quot;]

declare function acceptDate(date: DateType): void;
declare function produceDate(): DateType;
declare function modifyDate(date: DateType): DateType;

Then DateType is an indexed access into the type property of the DateTypeConfig interface. Now the DateTypeConfig interface doesn't currently have a specific type property, but it does have a string index signature of type string | Date, so to start with, DateType is string | Date.

So by default, a consuming app would see this union.

const d = produceDate();
// const d: string | Date
const m = modifyDate(d);
// const m: string | Date
acceptDate(m);

But the consuming app could re-open the DateTypeConfig interface and add an explicit type property whose type is some subtype of string | Date. (If this is in a module or your library is in a module you might need to use global augmentation or module augmentation to get the right namespace.)

For example:

interface DateTypeConfig {
    type: Date;
}

And then automatically, the DateType type alias becomes Date instead of string | Date:

const d = produceDate();
// const d: Date
const m = modifyDate(d);
// const m: Date
acceptDate(m);

Or instead the app could do this:

interface DateTypeConfig {
    type: string;
}

and then DateType would be string:

const d = produceDate();
// const d: string
const m = modifyDate(d);
// const m: string
acceptDate(m);

Playground link to code

huangapple
  • 本文由 发表于 2023年2月26日 23:08:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75572894.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定