当 extends 用于表示条件判断时,可以总结出以下规律
type result1 = 'a' extends 'abc' ? true : false // false
type result2 = 123 extends 1 ? true : false // false
狭窄类型 extends 宽泛类型
且宽泛类型中包含狭窄类型
时结果为 true,反之为 false。type result3 = string extends string | number ? true : false // true
type result4 = { a: true, b: false } extends { a: true } ? true : false // true
考虑如下 Demo 类型定义:
type Demo<T, U> = T extends U ? never : T
因为 'a' | 'b' | 'c' extends 'a'
是 false, 所以 Demo<'a' | 'b' | 'c', 'a'>
结果是 'a' | 'b' | 'c'
么?
查阅官网,其中有提到:
When conditional types act on a generic type, they become distributive when given a union type.
即当条件类型作用于泛型类型时,它们在给定联合类型时成为分配类型。用 JavaScript 来表达 'a' | 'b' | 'c' extends 'a'
的结果类似于:
function Demo(T, U) {
return T.map(val => {
if (val !== U) return val
return 'never'
})
}
Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']
此外根据 never 类型的定义 —— never 类型可分配给每种类型,但是没有类型可以分配给 never(除了 never 本身)。即 never | 'b' | 'c'
等价于 'b' | 'c'
。
因此 Demo<'a' | 'b' | 'c', 'a'>
结果并不是 'a' | 'b' | 'c'
而是 'b' | 'c'
。而 Demo 类型的声明其实就是 TS 官方提供的 Exclude<Type, ExcludedUnion>
。
如果想让 Demo<'a' | 'b' | 'c', 'a'>
的结果为 'a' | 'b' | 'c'
是否可以实现呢? 根据官网描述:
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
如果不想遍历泛型中的每一个类型,可以用方括号将 extends 关键字的每一侧括起来。
type Demo<T, U> = [T] extends [U] ? never : T
// result 此时类型为 'a' | 'b' | 'c'
type result = Demo<'a' | 'b' | 'c', 'a'>
在箭头函数中使用三元表达式时,从左向右的阅读习惯导致函数内容区若不加括号则会让使用方感到困惑。比如下方代码中 x 是函数类型还是布尔类型呢?
// The intent is not clear.
var x = a => 1 ? true : false;
在 eslint 规则 no-confusing-arrow 中,推荐如下写法:
var x = a => (1 ? true : false);
在 TypeScript 的类型定义中若在箭头函数中使用 extends 也是同理,一眼看如下代码也是比较绕,
Todo: 换一个 Demo
type CurryV0<P extends any[], R> =
(arg0: Head<P>) => HasTail<P> extends true ? CurryV0<Tail<P>, R> : R
因此在箭头函数中使用 extends 建议加上括号,对 code review 很有帮助。
type CurryV0<P extends any[], R> =
(arg0: Head<P>) => (HasTail<P> extends true ? CurryV0<Tail<P>, R> : R)
TypeScript 中有一个语法 [infer](),使用它可以实现推导类型的目的。一般会结合 extends 来使用 infer。比如:
todo:
// https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650. understanding it is difficult.
export type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<U>() => U extends Y ? 1 : 2) ? true : false
可以利用 tuple[number]
取得 tuple 的并集。
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type test = typeof tuple[number] // "tesla" | "model 3" | "model X" | "model Y"
可以利用 P in T[number]
进行遍历。
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: string
}
在对象中使用 |
与 &
,与在非对象中使用存在语义上的差异。
在集合对象中使用联合类型 |
,官网 working-with-union-types 有如下说明:
Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves.
type Foo = {
name: string
age: string
}
type Bar = {
name: string
age: string
gender: number
}
type result = keyof (Foo | Bar) // "name" | "age"
在集合对象中使用交集类型 &
,可以见 intersection-types 给出的 demo:
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
type ColorfulCircle = keyof (Colorful & Circle) // "color" | "radius"
结合 &
与 |
的使用,我们能立马写出比如类型 diff
type Diff<O, O1> = Omit<O & O1, keyof (O | O1)>
type Foo = {
name: string
age: string
}
type Bar = {
name: string
age: string
gender: number
}
type result = Diff<Foo, Bar> // { gender: number }
使用 declare 声明,可以将类型可以收拢在一处定义。使用 declare 可以定义以下类型:
declare const foo: number;
declare function greet(greeting: string): void;
注意的是,使用 declare 声明的变量与函数指向到具体的值,若要获取其类型定义,则需要使用 typeof。
type fooType = typeof foo
type greetType = typeof greet
declare namespace GreetingLib {
interface A {}
interface B {}
}