为啥 TS 中的泛型接口不能正确推断类型?
Posted
技术标签:
【中文标题】为啥 TS 中的泛型接口不能正确推断类型?【英文标题】:Why can't the generic interface in TS infer the type correctly?为什么 TS 中的泛型接口不能正确推断类型? 【发布时间】:2021-11-10 04:23:40 【问题描述】:一旦在泛型接口中使用“extends Explicit_value
”,TS 的类型系统将变得“愚蠢”,即使代码“100% 正确”。
function fn<T extends "a" | "b">(param: T): T
if (param === "a") return "a"/* <-- error
Type '"a"' is not assignable to type 'T'.
'"a"' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint '"a" | "b"'.
*/
else return "b"/* <-- error
Type '"b"' is not assignable to type 'T'.
'"b"' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint '"a" | "b"'.
*/
//that's ok:
function fn2<T extends string>(param: T): T
return param
//even this:
function fn3<T extends "a">(): T
return "a"/* <-- error
Type '"a"' is not assignable to type 'T'.
'"a"' is assignable to the constraint of type 'T',
but 'T' could be instantiated with a different subtype of constraint '"a"'.
*/
【问题讨论】:
【参考方案1】:我不会说这很愚蠢。它只是安全的。 看看这个例子:
function fn3<T extends "a">(): T
return "a" // error
const result = fn3<'a' & tag: 2 >().tag // 2
这意味着T
扩展a
但不等于'a'。
在上面的示例中,result
是 2
,但在运行时它等于 undefined
。
这就是 TS 给你一个错误的原因。通用参数应与运行时值绑定。就像您在第二个示例中所做的那样。
让我们看看你的第一个例子:
function fn<T extends "a" | "b">(param: T): T
if (param === "a") return "a"
else return "b"
错误:
'"a"' 可分配给类型'T' 的约束,但可以使用约束'"a" | 的不同子类型来实例化'T'。 "b"'
请记住,这并不意味着T
总是等于a
或b
。 T
可以是此约束/联合的任何子类型。
例如,您可以使用never
,它是类型系统的底层类型:
const throwError = () =>
throw Error('Hello')
fn<'a' | 'b'>(throwError())
fn
是否有可能返回a
或b
?不,它会抛出一个错误。也许这不是最好的例子,只是想向您展示不同子类型的含义。
让我们用不同的子类型来调用fn
:
declare var a: 'a' & tag: 'hello'
const result = fn(a).tag // undefined
你可能会说:嘿,你不按规则玩。 'a' & tag: 'hello'
类型在运行时无法表示。事实上并非如此。 tag
在运行时将始终为 undefined
。
但是,我们处于类型范围内。我们可以很容易地创建这种类型。
摘要
请不要将extends
视为相等的运算符。这只是意味着T
可能是已定义约束的任何子类型。
P.S. 类型在 TypeScript 中是不可变的。这意味着一旦您创建了带有某些约束的类型T
,您将无法返回具有其他约束的相同泛型参数T
。我的意思是,在您的第一个示例中,返回类型 T
不能只有 a
或只有 b
。永远是a | b
【讨论】:
谢谢。我从来没有想过像'a' & tag: 'hello'
这样的代码。【参考方案2】:
这是目前 TypeScript 类型检查系统的一个限制。这个例子在true | false
的情况下可以更好的理解:
function returnSelf<T extends true | false>(param: T): T
if (param === true)
type CUR_VAL = T; // original `T`, not `true` if it narrowed
return true;
else
type CUR_VAL = T; // original `T`, not `false` if it narrowed
return false;
如果您open the code in a playground 并将鼠标悬停在CUR_VAL
类型别名上,您会注意到它仍然等于T
,而不是缩小后的值。因此,当你尝试返回值时,它仍然认为T
是true | false
,这使得它无效。
【讨论】:
以上是关于为啥 TS 中的泛型接口不能正确推断类型?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在接口列表的泛型类型中使用私有嵌套类型不是“不一致的可访问性”?