我不会说这是愚蠢的。只是安全而已。
考虑这个例子:
function fn3<T extends "a">(): T {
return "a" // error
}
const result = fn3<'a' & { tag: 2 }>().tag // 2
代表着T
延伸a
但不等于“a”。
在上面的例子中,result
is 2
但在运行时它等于undefined
.
这就是 TS 给你一个错误的原因。通用参数应与运行时值绑定。就像您在第二个示例中所做的那样。
让我们看一下您的第一个示例:
function fn<T extends "a" | "b">(param: T): T {
if (param === "a") return "a"
else return "b"
}
错误:
'"a"' 可分配给类型 'T' 的约束,但 'T' 可以使用约束 '"a" | 的不同子类型来实例化。 “b”'
请记住,这并不意味着T
总是等于a
or b
. T
可以是该约束/联合的任何子类型。
例如,您可以使用never
这是类型系统的底层类型:
const throwError = () => {
throw Error('Hello')
}
fn<'a' | 'b'>(throwError())
有没有机会fn
将返回a
or b
?不,它会抛出错误。也许这不是最好的例子,只是想向您展示不同子类型的含义。
我们打电话吧fn
具有不同的子类型集:
declare var a: 'a' & { tag: 'hello' }
const result = fn(a).tag // undefined
你可能会说:嘿,你不遵守规则。类型'a' & { tag: 'hello' }
在运行时是无法表示的。事实上并非如此。tag
一直会undefined
在运行时。
但是,我们处于类型范围内。我们可以很容易地创建这样的类型。
SUMMARY
请不要治疗extends
作为一个等号运算符。这只是意味着T
可能是已定义约束的任何子类型。
P.S.TypeScript 中的类型是不可变的。这意味着一旦你创建了类型T
由于某些限制,您无法返回相同的通用参数T
与其他约束。我的意思是,在你的第一个例子中,返回类型T
不能只是a
或仅b
。它将永远是a | b