定义可全局扩展的TypeScript联合类型

0sgqnhkj  于 2023-11-20  发布在  TypeScript
关注(0)|答案(1)|浏览(156)

假设我有一个可以从多个模块扩展的导出数组,例如:

// values.ts
export const values = [];

export function useValue(value) {
  // ...
}

x

// module-1.ts
import { values } from './values.js';

values.push('foo', 'bar');

// @todo extend type
// module-2.ts
import { values } from './values.js';

values.push('baz');

// @todo extend type
// module-3.ts
import { useValue } from './values.js';

// (TS ERROR) should only accept: "foo" | "bar" | "baz".
useValue('qux');

的数据
是否可以创建一个(环境?)联合类型,通过向现有类型添加新值,可以由单个模块扩展?
我曾尝试使用接口和三重斜杠指令来实现预期的结果,但运气不佳。
我想要创建的是一个标签系统,其中核心功能由库模块提供,用户可以在应用程序的各个部分甚至其他库中引入自己的标签。但是,核心库应该看到引入的标签并正确验证所有函数调用。

sg2wtvxw

sg2wtvxw1#

据我所知,在你的库中获得这种类型扩展的唯一方法是将declaration merginginterface一起使用。(警告:我只是一个TypeScript的熟练工人。)
你的库的类型可能看起来像这样(在你的index.d.ts或类似的文件中):

declare module "yourlib" {
    interface PossibleValues {
        // Starts off empty (unless you have some defaults in `values`)
    }
    type Value = keyof PossibleValues;

    const values: Value[];

    function useValue(value: Value): void/* Or whatever it returns */;
}

字符串
注意Value的类型是如何从PossibleValues接口的内容中派生出来的。这是关键点。我们不关心PossibleValues中属性的类型,只关心它们的名称。
然后,module-1.ts看起来像这样:

import { values } from "yourlib";

declare module "yourlib" {
    interface PossibleValues {
        foo: true;
        bar: true;
    }
}

values.push("foo", "bar");


使用声明合并将foobar添加到接口,并将实际字符串推入valuespush调用是有效的,因为"foo""bar"Value的一部分,因为它们被添加到PossibleValues
然后module-2.ts做同样的事情:

import { values } from "yourlib";

declare module "yourlib" {
    interface PossibleValues {
        baz: true;
    }
}

values.push("baz");


最后,让我们测试一下module-3.ts的用法:

import { useValue } from "yourlib";

useValue("foo"); // OK
useValue("bar"); // OK
useValue("baz"); // OK
useValue("qux"); // Error: Argument of type '"qux"' is not assignable to 'Value'


不幸的是,这本质上有点脆弱,因为扩展模块很容易扩展接口,但无法将值添加到values,使类型系统和运行时稍微不同步。(幸运的是,他们不会犯另一个错误-如果他们没有扩展接口,values的类型不会让他们推送字符串。)
在一条评论中,你问到如何在项目中测试它而不将yourlib作为一个单独的库。你可以通过将yourlib作为一个模块并使用declare module来扩展它来做到这一点:
yourlib.ts

export interface PossibleValues {
    // Starts off empty (unless you have some defaults in `values`)
}
type Value = keyof PossibleValues;

export const values: Value[] = [];

export function useValue(value: Value): void {
    console.log(value);
}


注意,这不再是一个.d.ts文件,我们没有将它 Package 在declare module "yourlib"中,我们为数组和函数提供了实际的运行时值。
module-1.ts

import { values } from "./yourlib";

declare module "./yourlib" {
    interface PossibleValues {
        foo: true;
        bar: true;
    }
}

values.push("foo", "bar");


module-2.ts

import { values } from "./yourlib";

declare module "./yourlib" {
    interface PossibleValues {
        baz: true;
    }
}

values.push("baz");


除了import(“from "./yourlib"而不是from "yourlib"”)外,module-3.ts没有变化。

相关问题