假设我要编写一个只存储单个值的类。显然这是人为的; 我保持简单。实际上,这可能是一些集合,比如可以存储多个值的
Array
。
假设我需要包装一个
number
:
class Wrap {
value: number;
constructor(v: number) {
this.value = v;
}
}
现在我可以创建一个存储数字的实例,并且可以获取该数字:
const w = new Wrap(5);
console.log(w.value);
到目前为止,一切都很好。但是等等,现在我还想包装一个字符串
!如果我天真地尝试包装一个字符串,我会得到一个错误:
const w = new Wrap("foo");
产生了错误:
const w = new Wrap("foo");
^ string. This type is incompatible with the expected param type of
constructor(v: number) {
^ number
这样做不起作用是因为我告诉了Flow,
Wrap
只接受
numbers
。我可以将
Wrap
重命名为
WrapNumber
,然后复制它,称其为
WrapString
,并在内部更改
number
为
string
。但这很繁琐,现在我有两份相同的内容需要维护。如果每次想要包装新类型时都进行复制,这将很快失控。
但请注意,
Wrap
实际上并不对
value
进行操作。它不关心它是
number
还是
string
,或者其他什么类型。它只存在于存储和稍后返回值。这里唯一重要的不变量是您给出的值和您获取的值是
相同类型。使用的具体类型无关紧要,只要这两个值具有相同的类型。
因此,在这种情况下,我们可以添加类型参数:
class Wrap<T> {
value: T;
constructor(v: T) {
this.value = v;
}
}
T
在这里只是一个占位符。它的意思是,“我不关心你在这里放什么类型,但很重要的是,在使用T
的任何地方,它都是相同的类型。”如果我传递给您一个Wrap<number>
,您可以访问value
属性,并知道它是一个number
。同样地,如果我传递给您一个Wrap<string>
,您就知道该实例的value
是一个string
。有了这个新的Wrap
定义,让我们再试着包装一个number
和一个string
:
function needsNumber(x: number): void {}
function needsString(x: string): void {}
const wNum = new Wrap(5);
const wStr = new Wrap("foo");
needsNumber(wNum.value);
needsString(wStr.value);
Flow可以推断出类型参数,并且能够理解这里的所有内容将在运行时工作。如果我们尝试执行以下操作,我们也会得到预期的错误:
needsString(wNum.value);
错误:
20: needsString(wNum.value);
^ number. This type is incompatible with the expected param type of
11: function needsString(x: string): void {}
^ string
(在这里查看完整示例)
wNum.value = 'foo'
,那么静态类型检查器不会抱怨,这让我感到非常奇怪。我的期望是,由于wNum
是用数字实例化的,它应该强制执行此限制。 - user3056783string | number
而不仅仅是number
。如果你试图将其用作number
,那么就会出现错误。如果你想要在前面指定类型,你可以像这样注释变量:const wNum: Wrap<number> = new Wrap(5);
。然后Flow会在你尝试将wNum.value
设置为string
时立即给出错误提示。 - Nat MoteT
?这只是惯用语吗?T
代表什么? - Daniel LizikT
作为类型参数相当普遍。如果我要猜测,我会说它可能代表“类型”,但我不确定。 - Nat Mote