本系列作为《TypeScript 入门实战笔记》课程(见拉勾教育)的补丁,随时更新。
约束关系
这篇文章源于与同事的关于泛型类型推断的讨论,如下示例所示(playground):
type Func = (p: any) => void
function foo<T extends Func>(p: T) {}
function bar<T>(p: T) {}
foo((p = 1) => {}) // F1
foo((p: number = 1) => {}) // F2
bar((p = 1) => {}) // B1
bar((p: number = 1) => {}) // B2
如第 10 讲泛型里提到过的,(函数)泛型入参缺省的时候(即缺少 <泛型入参>泛型
,且未指定默认类型),会根据实参进行类型推断;所以在上面示例里,调用 foo 和 bar 函数的入参进行类型推断。
其中,注释 F2、B1、B2 的地方推断的入参类型都挺好理解,即 (p?: number) => void
。但 foo 函数类型比 bar 多了一层约束关系(extends Func
),它会约束 foo 函数入参的类型,但却没有约束入参(函数)类型的参数的类型(而是指定其类型是 any),所以注释 F1 推断的类型会是 (p?: any) => void
。
我们需要建立 foo 函数对入参(函数)类型入参类型的约束,比如给 Func 添加一个泛型入参:
type Func<P = any> = (p: P) => void
function foo<T extends Func>(p: T) {}
但这实际还是没有约束到参数的类型,我们需要进一步修改(playground):
type Func<P = any> = (p: P) => void
function foo<T>(p: Func<T>) {}
function bar<T>(p: T) {}
foo((p = 1) => {})
这样似乎就可以了,但实际推断出来的类型是 Func<unknown>
,等价于 (p: unknown) => void
。Interesting!(p = 1) => {}
作为一个函数整体来看,参数 p 的类型受默认值影响,会是(猜测是”类型缩小“,从 unknown 到 number) number;但在受到泛型入参约束的时候,似乎是被作为个体对待,直接拿到了明确的类型 unknown。这样就可以解释的清了(具体是不是这样,估计就要扒一下官方源码了,另外一个解释可以说是泛型入参缺省情况下是 unknown)。所以,foo 函数肯定无法把入参 (p = 1) => {}
类型推断为 (p?: number) => void
或者 (p: number | undefined) => void
。
此外大原则上,泛型入参和受它约束的入参,在做类型推断时,谁更明确(specified),入参的类型就受谁影响(但如果显式指定泛型入参,那么就不用类型推断了),如以下示例(plaground),受泛型入参默认类型影响,当入参类型不明确的时候,F1、B1,都推断出一个 any。
type Func<P = any> = (p: P) => void
function foo<T = any>(p: Func<T>) {}
function bar<T = any>(p: T = (() => void 0) as any as T) {}
foo((p = 1) => {}) // F1
foo((p: number = 1) => {})
bar() // B1
bar((p: number = 1) => {})
无效约束的妙用
见示例 playground:
type Func = <P>(p: P) => void
function foo<T extends Func>(p: T) {}
function bar<T>(p: T) {}
foo((p = 1) => {}) // ts(2322)
foo((p: number = 1) => {}) // ts(2345)
因为 Func 对应泛型的入参 P,并未真正约束到 foo 函数泛型入参(函数类型)T 的参数的类型,所以对 foo 的两次调用都会提示类型错误,因为 P 可能是任意类型。这就有意思了,比如可以用来实现 isEqual([playground]www.typescriptlang.org/play?#code/…
type isEqual<A, B> = (<P>() => P extends A ? 1 : 2) extends (<P>() => P extends B ? 1 : 2) ? true : false;
type E1 = isEqual<never, any>;
type E2 = isEqual<any, any>;
如以上示例所示,当且仅当 A 和 B 入参完全一致的时候,extends 成立(more issue)。