繁体   English   中英

使用 Map 在值中具有泛型类型的打字稿

[英]Typescript using Map with a generic type in the value

我有以下代码:

class A<T> {
  constructor(public value: T) {}
}

const map = new Map();
map.set('a', new A('a'));
map.set('b', new A(1));

const a = map.get('a');
const b = map.get('b');

目前, ab被推断为any 如果我将其更改为:

const map = new Map<string, A<????>>(); 

我需要传递A的泛型类型,但随后我丢失了value属性类型。 有办法实现吗?

const a = map.get('a') // should be inferred as A<string>
const b = map.get('b') // should be inferred as A<number>

TypeScript 的类型系统主要在值具有不随时间变化的类型的限制下运行。 所以如果你写let a: string = "hey"; ,您告诉编译器a并且将始终是string 你以后不能写a = 4; 没有编译时错误。 如果你只写let b = "hey"; 如果没有注释类型,编译器会将类型推断string ,稍后再写b = 4; 你会得到一个编译器错误。 如果你想让一个变量有时保存一个string而其他时候保存一个number ,你应该自己将它注释为string | number 声明它时的string | number ,如let c: string | number = "hey"; let c: string | number = "hey"; 然后c = 4; 会没事的。

默认情况下,如果你有一个对象,你需要在声明它时提前告诉编译器属性类型是什么。 所以下面会给你一个错误:

let o = {}; // type {}
o.a = new A('a'); // error, {} has no "a" property
o.b = new A(1); // error, {} has no "b" property

以下将起作用:

let o2 = { a: new A('a'), b: new A(1) }; // okay
// let o2: { a: A<string>; b: A<number>; }

使这种限制更容易处理的 TypeScript 的一个特性是控制流类型分析,其中编译器通过将值限制为子类型来看到暂时缩小的类型 如果我写let c: string | number = "hey" let c: string | number = "hey"然后我可以写c.toUpperCase()没有错误,因为编译器看到赋值已经暂时缩小了string | number c string | number string | numberstring 如果没有这样的分析,那么您需要使用像(c as string).toUpperCase()这样的类型断言。 但是即使是控制流类型分析也不能随意改变一个值的类型; 它只能缩小它(或者可能在重新分配时重新扩大回注释/推断类型)。 因此,您可以使用它为对象添加属性,但不能更改属性类型,甚至不能删除属性。 如果您希望能够删除一个属性,那么您在添加它时需要您添加的属性是可选的。

TypeScript 3.7 引入了断言函数,这些函数允许用户定义的函数根据参数的缩小来自定义控制流。 您可以使用它来编写自定义setProp函数,该函数将属性添加到现有对象。 由于我知道您要删除这些属性,因此我会将它们添加为可选:

function setProp<O extends object, K extends PropertyKey, V>(
    obj: Partial<O>,
    key: Exclude<K, keyof O>, value: V
): asserts obj is Partial<O & Record<K, V>> {
    (obj as any)[key] = value as any;
}

它是这样工作的:

const map = {}
map.a = new A('a'); // error, can't do this
// but can do this:
setProp(map, 'a', new A('a'));
// now that it's been set you can re-set it to the same type
map.a = new A('b');

setProp(map, 'b', new A(1));
setProp(map, 'c', "a string");

您可以仅通过属性访问来获取和删除属性,请记住,由于它们是可删除的,因此您需要在访问它们之前检查undefined

map.a && console.log(map.a.value); // b
map.b && console.log(map.b.value); // 1
delete map.b; // okay        
map.c && console.log(map.c.toUpperCase()); // A STRING 

所以这就是我可能的处理方式。 不过,最好在声明map时提前设置类型。 基于控制流的收缩不如仅仅注释开始的类型那么健壮。


最后,如果您真的想使用Map而不是普通对象,您可以为Map编写自己的自定义类型,因为内置的Map假设所有值都是相同的类型。 但这已经很长了。 在下面的 Playground Link 中,我有一个可能的实现以及上面的其余代码。

好的,希望这有助于给你一些想法。 祝你好运!

游乐场链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM