Skip to content
索引

进阶语法

加号减号

一些内置类型中,可能会出现+或者-这些符号,例如:

ts
type Required<T> = {
  [P in keyof T]-?: T[P]
}
type Person = {
  name: string;
  age?: number;
}

// 结果:{ name: string; age: number; }
type result = Required<Person>
type Required<T> = {
  [P in keyof T]-?: T[P]
}
type Person = {
  name: string;
  age?: number;
}

// 结果:{ name: string; age: number; }
type result = Required<Person>

-?是去掉类型中属性后面的?,整个Required的实际效果是去掉T类型中所有属性键后面的?,让所有属性变成必填的

in

in操作符的右侧通常跟一个联合类型,可以使用in来迭代这个联合类型,如下:

ts
// 仅演示使用
in 'name' | 'age' | 'sex'
'name' // 第一次迭代结果
'age'  // 第二次迭代结果
'sex'  // 第三次迭代结果
// 仅演示使用
in 'name' | 'age' | 'sex'
'name' // 第一次迭代结果
'age'  // 第二次迭代结果
'sex'  // 第三次迭代结果

keyof

keyof T表示获取T类型中所有属性键,这些属性键组合成一个联合类型,例如:

ts
type Person = {
  name: string;
  age: number;
}
// 'name' | 'age'
type result = keyof Person
type Person = {
  name: string;
  age: number;
}
// 'name' | 'age'
type result = keyof Person

TS中的keyof T,有点类似JavaScript中的Object.keys(),它们的共同点都是获取属性键的集合,只不过keyof T得到的结果是一个联合类型,而Object.keys()得到的是一个数组

in keyof

[P in keyof T]:表示遍历T中的每一个属性键,每次遍历时属性键取名为P,这和JavaScript中的for in非常类似:

ts
type required<T> = {
  [p in keyof T]-?: T[p]
}
type required<T> = {
  [p in keyof T]-?: T[p]
}

extends

一般有两种用法:条件类型类型约束

条件类型

常见的条件类型表现形式如下:

ts
T extends U ? 'Y' : 'N'
T extends U ? 'Y' : 'N'

条件类型有点像JavaScript中的三元表达式,事实上它们的工作原理是类似的,例如:

ts
type res1 = true extends boolean ? true : false                  // true
type res2 = 'name' extends 'name'|'age' ? true : false           // true
type res3 = [1, 2, 3] extends { length: number; } ? true : false // true
type res4 = [1, 2, 3] extends Array<number> ? true : false       // true
type res1 = true extends boolean ? true : false                  // true
type res2 = 'name' extends 'name'|'age' ? true : false           // true
type res3 = [1, 2, 3] extends { length: number; } ? true : false // true
type res4 = [1, 2, 3] extends Array<number> ? true : false       // true

在条件类型中,有一个特别需要注意的东西就是:分布式条件类型,如下:

ts
type Extract<T, U> = T extends U ? T : never;
type type1 = 'name'|'age'
type type2 = 'name'|'address'|'sex'

// 结果:'name'
type test = Extract<type1, type2>

// 推理步骤
'name'|'age' extends 'name'|'address'|'sex' ? T : never
step1: ('name' extends 'name'|'address'|'sex' ? 'name' : never) => 'name'
step2:  ('age' extends 'name'|'address'|'sex' ? 'age' : never)   => never
result: 'name' | never => 'name'
type Extract<T, U> = T extends U ? T : never;
type type1 = 'name'|'age'
type type2 = 'name'|'address'|'sex'

// 结果:'name'
type test = Extract<type1, type2>

// 推理步骤
'name'|'age' extends 'name'|'address'|'sex' ? T : never
step1: ('name' extends 'name'|'address'|'sex' ? 'name' : never) => 'name'
step2:  ('age' extends 'name'|'address'|'sex' ? 'age' : never)   => never
result: 'name' | never => 'name'

代码详解:

  • T extends U ? T : never:因为T是一个联合类型,所以这里适用于分布式条件类型的概念。根据其概念,在实际的过程中会把T类型中的每一个子类型进行迭代,如下:
ts
// 第一次迭代:
'name' extends 'name'|'address'|'sex' ? 'name' : never
// 第二次迭代:
'age' extends 'name'|'address'|'sex' ? 'age' : never
// 第一次迭代:
'name' extends 'name'|'address'|'sex' ? 'name' : never
// 第二次迭代:
'age' extends 'name'|'address'|'sex' ? 'age' : never
  • 在迭代完成之后,会把每次迭代的结果组合成一个新的联合类型(根据never类型的特点,最后的结果会剔除掉never),如下:
ts
type result = 'name' | never => 'name'
type result = 'name' | never => 'name'

extends keyof

类型约束,经常和泛型一起使用:

ts
U extends keyof T
U extends keyof T

keyof T是一个整体,它表示一个联合类型。U extends Union这一整段表示U的类型被收缩在一个联合类型的范围内

ts
interface Person {
  age: number
  name: string
}
const person: Person = {
  age: 22,
  name: "Tobias"
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
const name1 = getProperty(person, "name")
const gender = getProperty(person, "sex") //报错 
interface Person {
  age: number
  name: string
}
const person: Person = {
  age: 22,
  name: "Tobias"
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}
const name1 = getProperty(person, "name")
const gender = getProperty(person, "sex") //报错 

infer

infer关键词的作用是延时推导,它会在类型未推导时进行占位,等到真正推导成功后,它能准确的返回正确的类型

为了更好的理解infer关键词的用法,使用ReturnType这个例子来说明,ReturnType是一个用来获取函数返回类型的工具。

ts
type ReturnType<T> = T extends (...args: any) => infer R ? R : never

const add = (a: number, b: number): number => {
  return a + b
}
// 结果: number
type result = ReturnType<typeof add>
type ReturnType<T> = T extends (...args: any) => infer R ? R : never

const add = (a: number, b: number): number => {
  return a + b
}
// 结果: number
type result = ReturnType<typeof add>

代码详解:

  • T extends (...args: any) => infer R:如果不看infer R,这段代码实际表示:T是不是一个函数类型。
  • (...args: any) => infer R:这段代码实际表示一个函数类型,其中把它的参数使用args来表示,把它的返回类型用R来进行占位。 如果T满足是一个函数类型,那么我们返回其函数的返回类型,也就是R;如果不是一个函数类型,就返回never

typeof

获取一个变量的类型,通常用于获取一个对象函数的类型

ts
const add = (a: number, b: number): number => {
  return a + b
}
const obj = {
  name: 'AAA',
  age: 23
}

// 结果:(a: number, b:number) => number
type t1  = typeof add
// 结果:{ name: string; age: number; }
type t2 = typeof obj
const add = (a: number, b: number): number => {
  return a + b
}
const obj = {
  name: 'AAA',
  age: 23
}

// 结果:(a: number, b:number) => number
type t1  = typeof add
// 结果:{ name: string; age: number; }
type t2 = typeof obj

空值判断运算符

??与 js中的空值判断运算符一样

同样是只有运算符左侧的值为null或undefined时,才会返回右侧的值,否则返回左侧的值

ts
const value: number = 0
const value1: string = ""
const value2: number = null
const value3: string = null
console.log(value ?? "value为空值") // 0
console.log(value1 ?? "value为空值") // ""
console.log(value2 ?? "value为空值") // value为空值
console.log(value3 ?? "value为空值") // value为空值
const value: number = 0
const value1: string = ""
const value2: number = null
const value3: string = null
console.log(value ?? "value为空值") // 0
console.log(value1 ?? "value为空值") // ""
console.log(value2 ?? "value为空值") // value为空值
console.log(value3 ?? "value为空值") // value为空值

[name:type]:type

在不确定对象属性名与对应属性值类型的情况下,形参设置对象的属性名为string类型(一般情况下都是string类型),而属性值的类型则可能有很多种

ts
interface ccache {
    [a:string]:number
}
const cache:ccache = {}
// 类型正确
cache.name = 1
// 不报错
cache.name = "1"
interface ccache {
    [a:string]:number
}
const cache:ccache = {}
// 类型正确
cache.name = 1
// 不报错
cache.name = "1"

常用内置类型

位于node_modules/typescript/lib/lib.es5.d.ts

Readonly

把每个属性都变成只读

ts
// 使用
type A  = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}
// 实现
type Readonly<T> = {
    readonly [P in keyof T]: T[P] // keyof获取T上的键值集合 in表示循环keyof获取的键值
}
// 使用
type A  = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}
// 实现
type Readonly<T> = {
    readonly [P in keyof T]: T[P] // keyof获取T上的键值集合 in表示循环keyof获取的键值
}

Pick

只保留自己选择的属性

typescript
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}

Omit

排除已选的属性

typescript
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}

Partial

通过泛型让目标类型中的所有属性变为可选

typescript
type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}
type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}

Required

通过泛型让目标类型中的所有属性变为必选

typescript
type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}
type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}

Record

创建一个类型,Record<K, T>,K代表新类型键的类型,T代表新类型值的类型

ts
// 示例一,对象a键的类型为字符串,值的类型为数字
type A1 = Record<string, number>
const a: A1 = {
  a: 1
}

// 示例二,对象result键的类型是"home" | "about" | "contact",值的类型为PageInfo
interface PageInfo {
  title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

// 示例三,对象result键的类型是 'A' | 'B' | 'C',值的类型为数字
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
  A: 1,
  B: 2,
  C: 3
}
// 示例一,对象a键的类型为字符串,值的类型为数字
type A1 = Record<string, number>
const a: A1 = {
  a: 1
}

// 示例二,对象result键的类型是"home" | "about" | "contact",值的类型为PageInfo
interface PageInfo {
  title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

// 示例三,对象result键的类型是 'A' | 'B' | 'C',值的类型为数字
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
  A: 1,
  B: 2,
  C: 3
}

实现

ts
type Record<K extends keyof any, T> = {
    [P in K]: T
}
type Record<K extends keyof any, T> = {
    [P in K]: T
}

ReturnType

获取T的返回值的类型

ts
type A1= ReturnType<()=>number> // number
type A1= ReturnType<()=>number> // number

NonNullable

剔除undefined和null

ts
type A1 = NonNullable<number | string | null | undefined> // number|string
type A1 = NonNullable<number | string | null | undefined> // number|string

Exclude

过滤T中和K相同(或兼容)的类型

ts
type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉
type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉

Extract

用法

Extract<T, U>用来取联合类型TU的交集,用法如下:

ts
type Person = {
  name: string;
  age: number;
  address: string;
}

// 结果:'age'|'address'
type ExtractResult = Extract<keyof Person, 'age'|'address'|'sex'>
type Person = {
  name: string;
  age: number;
  address: string;
}

// 结果:'age'|'address'
type ExtractResult = Extract<keyof Person, 'age'|'address'|'sex'>

实现方式

ts
type MyExtract<T, U> = T extends U ? T : never
type MyExtract<T, U> = T extends U ? T : never

代码详解:

  • T extends U:此代码会自动将T的子类型进行分发,例如:
js
T extends U
=> 'name'|'age'|'address' extends 'age'|'address'|'sex' ? T : never
=> (
  'name' extends 'age'|'address'|'sex' ? 'name' : never |
  'age' extends 'age'|'address'|'sex' ? 'age' : never |
  'address' extends 'age'|'address'|'address' ? 'age' : never
)
=> 'age'|'address'
T extends U
=> 'name'|'age'|'address' extends 'age'|'address'|'sex' ? T : never
=> (
  'name' extends 'age'|'address'|'sex' ? 'name' : never |
  'age' extends 'age'|'address'|'sex' ? 'age' : never |
  'address' extends 'age'|'address'|'address' ? 'age' : never
)
=> 'age'|'address'

declare

1.全局定义类型

2.覆盖默认类型

解构赋值设置类型

ts
const { dst }: { dst: string } = data.trans_result[0]
const { dst }: any = data.trans_result[0]
const { dst }: { dst: string } = data.trans_result[0]
const { dst }: any = data.trans_result[0]

is类型保护

在使用类型保护时,TS 会进一步缩小变量的类型。例子中,将类型从 any 缩小至了 string

ts
function isString(test: string): boolean {
  return typeof test === "string"
}
function example(foo: any) {
  if (isString(foo)) {
    // 编译不会出错,运行时出错
    console.log(foo.push(2))
  }
}
example("hello world")
function isString(test: string): boolean {
  return typeof test === "string"
}
function example(foo: any) {
  if (isString(foo)) {
    // 编译不会出错,运行时出错
    console.log(foo.push(2))
  }
}
example("hello world")
ts
function isString(test: string): test is string {
  return typeof test === "string"
}
function example(foo: any) {
  if (isString(foo)) {
    // ts直接提示 类型“string”上不存在属性“push”
    console.log(foo.push(2))
  }
}
example("hello world")
function isString(test: string): test is string {
  return typeof test === "string"
}
function example(foo: any) {
  if (isString(foo)) {
    // ts直接提示 类型“string”上不存在属性“push”
    console.log(foo.push(2))
  }
}
example("hello world")

unknown

很多情况下, 我们可以使用 unknown 来替代 any , 既灵活, 又可以继续保证类型安全

ts
export const isString = (val: unknown): val is string => typeof val === 'string'
export const isString = (val: unknown): val is string => typeof val === 'string'

声明文件

用于为已有js或ts文件提供类型声明,如果多个ts文件都用到相同的类型,就适合创建一个类型声明文件,实现类型共享

  1. 创建后缀名为.d.ts的类型声明文件
  2. 使用export导出类型
  3. ts文件中通过import导入,并且可以省略.d.ts后缀名
  4. 如果不省略.d.ts后缀,ts会报错,提示导入路径不能以“.d.ts”扩展名结束。考虑改为导入“./type.js”。ts(2691)
  5. 因此类型声明文件应该避免与ts文件同名,导致import时引用错误

示例

新建type.d.ts,export导出

ts
type Props = { x: number, y: number }
export { Props }
type Props = { x: number, y: number }
export { Props }

新建a.ts,import导入类型

ts
import { Props } from "./type"
const a: Props = { x: 1, y: 2 }
import { Props } from "./type"
const a: Props = { x: 1, y: 2 }

Released under the MIT License.