Typescript 是否允许 Map 具有不同的泛型?

Does Typescript allow Map to have different generics?

提问人:taffec 提问时间:11/13/2023 最后编辑:taffec 更新时间:11/13/2023 访问量:39

问:

我是 Typescript 的新手,并试图学习如何创建一个具有类型属性的类,其中 .我在为它编写构造函数时遇到了麻烦,因为 Typescript 希望我声明什么是,即使我选择了 or .我也认为我会遇到麻烦,因为可能包含多个具有不同的数组。当传递到构造函数而不是方法时,它也会失败。Map<string, Val<T>>type Val<T> = string | number | Array<T><T>stringnumberMapT

我的代码看起来像这样

type Val<T> = string | number | Array<T>

这会引发错误

export class MyClass<T> {
    model;

    constructor(model: [string, Val<T>][]) {
        this.model = new Map<string, Val<T>>(model);
    }
}

const c = new MyClass([
    ["key1", "val1"],
    ["key2", 99],
    ["key3", ["cat", "dog"]],
    ["key4", [1,2,3]],
]);
src/fakefuse.ts:38:15 - error TS2322: Type 'number' is not assignable to type 'string'.
38     ["key4", [1,2,3]],
src/fakefuse.ts:38:17 - error TS2322: Type 'number' is not assignable to type 'string'.
38     ["key4", [1,2,3]],
src/fakefuse.ts:38:19 - error TS2322: Type 'number' is not assignable to type 'string'.
38     ["key4", [1,2,3]],

奇怪的是,出于某种原因,使用方法与构造函数进行编译时没有任何错误

class MyClass<T> {
    model;

    constructor() {
        this.model = new Map<string, Val<T>>();
    }

    init(model: [string, Val<T>][]) {
        this.model = new Map<string, Val<T>>(model);
    }
}

const c = new MyClass();
c.init([
    ["key1", "val1"],
    ["key2", 99],
    ["key3", ["cat", "dog"]],
    ["key4", [1,2,3]],
]);

这似乎只有在将值直接传递给 !如果你尝试了一个变量,你会得到这个。c.init

const c = new MyClass();
const aVar = [
    ["key1", "val1"],
    ["key2", 99],
    ["key3", ["cat", "dog"]],
    ["key4", [1,2,3]],
];
c.init(aVar);
src/fakefuse.ts:45:8 - error TS2345: Argument of type '((string | number)[] | (string | string[])[] | (string | number[])[])[]' is not assignable to parameter of type '[string, Val<unknown>][]'.
  Type '(string | number)[] | (string | string[])[] | (string | number[])[]' is not assignable to type '[string, Val<unknown>]'.
    Type '(string | number)[]' is not assignable to type '[string, Val<unknown>]'.
      Target requires 2 element(s) but source may have fewer.

45 c.init(aVar);

有人可以帮助我了解我应该如何做到这一点吗?我觉得也许这就是作用,但我真的不知道它是如何工作的?extends

JavaScript TypeScript 对象 泛型

评论


答:

2赞 enzo 11/13/2023 #1

奇怪的是,出于某种原因,使用方法与构造函数进行编译时没有任何错误

那是因为当你这样做时

const c = new MyClass();

您不会将任何类传递给类型参数,使其成为 类型。MyClass<unknown>

因此,当你做之后

c.init(...);

它会接受任何东西,因为理论上任何东西都可以分配给 .unknown


为什么当你把它直接传递给构造函数时它不起作用?因为在创建对象时,如果你不传递任何类型参数,Typescript 会尝试从构造函数参数中推断:

const c = new MyClass([
    ["key1", "val1"],
    ["key2", 99],
    ["key3", ["cat", "dog"]],
    ["key4", [1,2,3]],
]);

对于列表中的每一项,Typescript 将尝试推断它们的类型:第一个元素是 a,第二个元素是 a,第三个元素是 .它在那里停顿并推断为 ,使 .stringnumberArray<string>TstringcMyClass<string>

它现在查看第四个元素 a ,并正确地抱怨,因为如果推断为 ,那么它必须只包含 s、s 或 s。Array<number>TstringstringnumberArray<string>


为了不让它抱怨,你可以显式地将类的类型参数声明为 和 之间的并集:stringnumber

type Val<T> = string | number | Array<T>

export class MyClass<T> {
    model;

    constructor(model: [string, Val<T>][]) {
        this.model = new Map<string, Val<T>>(model);
    }
}

const c = new MyClass<string | number>([
    ["key1", "val1"],
    ["key2", 99],
    ["key3", ["cat", "dog"]],
    ["key4", [1,2,3]],
]);

评论

0赞 taffec 11/14/2023
非常感谢您的回答,这是有道理的。如果 Array 的类型确实是通用的,我该怎么办?在此示例中,它是一个字符串或数字,但实际上它可以是任何内容。我预计大多数情况下,它是用户将提供的不同类型的对象。
0赞 enzo 11/14/2023
该类型是要避免的类型,但是如果您确实有一个有效的用例,即“该类型可以是任何东西”,那么您可以使用代替 .anyMyClass<any>MyClass<string | number>