[英]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');
目前, a
和b
被推断为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 | number
到string
。 如果没有这样的分析,那么您需要使用像(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.