TypeScript

242 阅读22分钟

typescript简介

typescript的功能是静态类型校验,校验时间在代码编写阶段。

安装: npm install typescript -g 或者 yarn add typescript -g

查看版本号: tsc -v

image.png

本地测试

初始化ts项目:tsc -init

image.png

新建01.ts

image.png

初体验:

ts会根据初始赋值来确定变量的类型,并且str之后只能是string类型。

image.png

ts编译成js:tsc 文件名

image.png

编译出的js文件在根目录,建议分开:

找到tsconfig.json文件,全局搜outDir,它的作用是:ts编译成js后,将js文件放在哪,./是在当前目录下 image.png

将outDir解除注释,改成outDir:"./js/",将编译后的js文件放在js文件夹下,现在去创建js文件夹&修改outDir:

image.png

删除01.js,并在命令行执行:tsc -p tsconfig.json --watch

该命令意思是:实时监测,ts文件改完后保存,会根据outDir指定的目录,生成对应的js文件。

ts原始类型

变量p1的类型取决于初始赋值给它的值是什么类型,在这里是string image.png const声明常量,常量不能修改,所以p2的类型就是它的值6 image.png

ts原始类型有哪些?

js基础数据类型:number,string,boolean,undefined,null,symbol

ts原始类型就是js这些

string

let str1 = "" // 自动推导为string
let str2:string = ""
 // String推荐小写,大写留给接口、类型别名,规范把
let str3:String = ""

str1 = 2 // 报错,number不能赋值给string

number && boolean && symbol

let num:number = 1
let bool:boolean = true
// Symbol创建的值永远不相等
let sym:symbol = Symbol("2")

undefined

let und:undefined;
und = null // 报错

// und的值只能是undefined,''/null都不行
image.png

null

// nul的值只能为null
let nul:null;

需要注意,undefined&&null

null和undefined直接赋值给变量,变量的类型不是值本身而是any,就是任意类型,一旦被设置了any,ts将毫无意义。

image.png image.png

ts非原始数据类型

object,Object,{}

object(推荐)

// 小写的o,object
let obj:object = { a: 1 } // √
let arr:object = [1] // √
let num:object = 2 // × 不能将number赋给object

Object && {}

Object类型和{}类型一致,基本数据类型校验都能通过,除了undefined 和 null,类型太过广泛,不被推荐

// 大写的O,Object
let sym:{} = Symbol("z") // √
let str:{} = "" // √
let nul:{} = null // 报错,不能将null类型赋值给‘{}’类型
let und:{} = undefined // 报错

let bol:Object = true // √
let num:Object = 2 // √
let nul:Object = null // 报错
let und:Object = undefined // 报错
image.png

数组类型

第一种写法:

// number[],数组里都是number类型
let arr:number[] = [3, 6, 9]
arr[0] = 2 // √
arr[1] = true // 报错,不能将boolean类型赋值给number类型

第二种写法:

// 泛型,类型参数化,功能等效于number[]
let arr:Array<number> = [5, 6, 7]

元组:

// 类型一一对应,若数组长度大于或小于自身长度则报错
let arr:[number, string, boolean] = [1, "", false]

联合类型

联合类型 -> '|', 或

// 变量numAndBol为number或boolean类型
let numAndBol: number | boolean = 1
numAndBol = false
numAndBol = "" // 报错

let variable:1|'2' = 1 // √
variable = '2' // √
variable = 2 // ×

变量variable的类型是值,为1或'2' image.png

// 可以都有,至少得有1个
let obj: {a:1} | {b:2};
obj = {a:1} // √
obj = {b:2} // √
obj = {c: 3} // 报错
obj = {a:1, b:2} // √

交叉类型

交叉类型 -> '&', 与

// 必须都要有
let obj:{name: string, gender: number} & {isSmart: boolean};
// 类型不对,缺少或多出,都会报错
obj = { name: 'zs', gender: 1, isSmart: true} // √
obj = { name: 'zs', gender: 1 } // ×
// 如果一个属性出现多次类型设置,需要同时满足
let obj:{name: string, gender: number} & {gender: 2};
obj = { name: 'hh', gender: 1 } // ×
// 所以这里gender只能为2
obj = { name: 'xx', gender: 2 } // √

any

any 类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any 能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型。

let power: any;

// 赋值任意类型
power = '123';
power = 123;

// 它也兼容任何类型
let num: number;
power = num;
num = power;

unknown

使用 unknown 类型代替 any 类型 ???

  1. any 是类型系统提供给我们的一个妥协方案,在无法确定当前类型时就使用 any ,它可以赋给任何类型值,也可以接受任何类型值
  2. 使用 any 替代之后,这样我们就失去了类型检查的作用,在可能出错的地方也不会发现错误。
image.png
  1. 使用 any 还会造成类型污染的问题,any 类型的对象会导致后续的属性类型都会变成 any
image.png

失去了类型检查作用之后,TS 不会在开发或者编译时提示哪里可能出错,既然我们选择了使用 TS,那么在开发中就尽量避免使用 any ,以便 TS 能够帮助我们做更多的事情。

所以从 TypeScript 3.0 起就引入了一个新的基础类型 unknown 作为一个类型安全的 any 来使用。任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。

image.png

如果要把 unknown 类型值赋给 unknown 或者 any 之外的其它类型,或者对 unknown 类型执行方法调用或者属性读取之类的操作,都必须先使用条件控制流或者类型断言来收窄 unknown 到指定的类型

image.png

void

void:没有返回值

// 无返回值相当于就是return undefiend
function fn ():void {}
let friday = fn()
// 只能是undefined,是其他都会报错
friday = undefined
image.png

never

never:产生异常可以使用它,同时never是所有类型的子类型,说白了never就是没有类型

猜想: image.png

image.png

验证: image.png

never是所有类型的子类型?验证:

image.png

image.png

image.png

接口类型 interface

interface是用于自定义类型的,可以分别给对象、数组、函数使用。

给对象用

// 利用接口,自定义类型
interface ObjItf{
  name: string,
  age: number,
  isGirl: boolean
}

// 3个属性必须都要有,多或少或类型不对都报错
let obj:ObjItf = {
  name: 'xiaohua',
  age: 24,
  isGirl: false
} // √

给数组用

interface ArrItf {
// 利用接口来给数组定类型时的固定写法
// [index:number]下标类型:值类型
  [index:number]: number | string
}

let arr:ArrItf = [1, 2, '3']

给函数用

interface FuncItf{
// 固定写法
// 形参及类型:返回值类型
  (name:string): void
}
let fn:FuncItf = (name:string) => {}
// 或 let fn:FuncItf = (name:string):void => {}
fn("")

接口的继承、同名、缺省、只读

接口继承

interface NameItf{
  name: string
}

interface GenderItf{
  gender: number
}
// extends:继承
// InfoItf接口拥有被继承两个接口的所有属性和对应类型
interface InfoItf extends NameItf, GenderItf {
  isHappy: boolean
}

// 缺少或多出或类型不对或属性名不对则报错
let person:InfoItf = {
  name: '张三',
  gender: 1,
  isHappy: true
} // √

let person:InfoItf = {
  name: '张三',
  gender: 1
} // ×

接口同名

interface ObjItf{
  phone: number 
}
interface ObjItf{
  age: string
}

let obj:ObjItf = {
 phone: 7999,
} // ×,缺少age

obj = {
 age: '24'
} // ×,缺少phone

// 变量类型是接口,且为同名接口,
// 必须要包含里面所有属性且类型正确
obj= {
 phone: 7999,
 age: '24'
} // √

缺省 和 只读

interface ObjItf {
  readonly name: string // readonly 设置属性为只读
  age?: number // 缺省 ? 可以没有这个属性
  /*
    若使用了ObjItf接口,需要定义fn函数,参数
    是布尔值,返回值也是布尔值。
  */
  fn: (bool: boolean) => boolean
}

// 可以不写age,因为缺省了
let obj:ObjItf = {
  name: 'xh',
  fn: bool => bool
} // √

// 报错,因为name是只读属性
// Cannot assign to 'name' because it is a
// read-only property
obj.name = '' // ×

联合交叉类型

console.log(1 || 2&&3) // 1 || 3 -> 1

&& 优先级高于 ||,& 同样优先级高于 |

// 满足联合类型‘|’左边或右边的,& 需同时满足
let obj: {name: string} & {age: number} |
 {name: string, age: string, bol: boolean}

obj = {
  name: 'sz',
  age: 24
} // √

obj = {
  name: 'sz',
  bol: false
} // ×

obj = {
 name: 'hh',
 age: '24',
 bol: false
} // √

类型别名 type

// 自定义类型
type StrOrNum = string | number
let num:StrOrNum = 2 // √
num = '2' // √

type ObjType = {
  isHappy: boolean,
  fn?: (name:string) => number
}

let obj:ObjType = {
  isHappy: false,
} // √

obj = {
  isHappy: true,
  fn: MYName => 666
} // √

interface和type区别:

  1. 都可以用来自定义类型
  2. 类型别名type不支持重复定义,接口可以

image.png 3. 类型别名、接口 混合使用

interface NameItf{
  name: string
}
type NameType = NameItf['name'] // string

let n:NameType = 'zs' // √
// Type 'number' is not assignable to type 'string'
n = 24 // ×

函数

// 参数1,2是number类型,返回值也是。
function fn(arg1:number, arg2:number):number{
  return arg1 + arg2
} // √

const fn2 = (a: boolean, b: string):number => {
  return 1
} // √

② 接口定义函数类型

interface FnItf {
  (name:string): number
}
let fn:FnItf = (name:string) => {
  return 1
}
fn("")

③ 类型别名定义函数类型

type FnType = (name:string) => number
let fn:FnType = (name:string) => {
  return 1
}
fn("")

④ 函数作为对象属性

interface ObjItf{
  fn: FnItf
}
interface FnItf{
  (bol:boolean): void
}

const obj:ObjItf = {
  fn: bol => {}
}

obj.fn(true) // √
// Argument of type 'number' is not assignable
// to parameter of type 'boolean'
obj.fn(1) // ×

或者这样

type ObjItf = {
  fn: FnItf
}
interface FnItf{
 (bol:boolean): void
}
const obj:ObjItf = {
 fn: bol => {}
}

或者这样(写法很多,看自己喜好)

type ObjItf = {
  fn: FnItf
}
type FnItf = (bol:boolean) => void
const obj:ObjItf = {
  fn: bol => {}
}

函数参数

① 默认参数

const fn = (a:number, b = 1) => {
  return a + b
}
fn(1,2)

fn的类型

image.png

② 缺省参数

const fn = (c:boolean, d?:string) => {
  if (d) {
    return c + d
  }
  return c
}
fn(true, '2')
fn(false)

fn的类型 image.png

③ 剩余参数

const fn = (a: boolean, ...args: Array<number>) => {
  console.log(args); // [2,3,4]
}
fn(true, 2,3,4)

fn的类型

image.png

ts中的Promise

const p = new Promise((resolve, reject) => {
  resolve({
    code: 0,
    data: [{ a: 1, b: 2 }, { a: 6, b: 9 }]
  })
})

// res: unknown
p.then(res => {
  if (!res.code) {}
})

res是unknown类型,所以不能‘.’,不能调用方法。 image.png

image.png 所以需要给res设置类型:

// 给res定义类型
interface ResItf{
  code: number,
  data: {a: number, b: boolean}[],
  msg: string
}

const p:Promise<ResItf> = new Promise((resolve, reject) => {
  resolve({
    code: 0,
    data: [{ a: 1, b: true }, { a: 6, b: false }],
    msg: 'success'
  })
})

p.then(res => {
  if (!res.code) {}
})

枚举

枚举不是用来定义类型,而是用来列举数据用的

enum Xxx{
  x: 1,
  y: 2
}
// Xxx.y

没有使用枚举前:

let code:number|string = 200
if (code === 200) {
  console.log("成功")
} else if (code === 400) {
  console.log("失败,请求参数问题")
} else if (code === 500) {
  console.log("失败,服务器问题")
}

使用枚举后:

// 好处是状态统一管理,易于维护
enum StatusCode{
  success = 200,
  paramsError = 400,
  serverError = 500
}
let code:number|string = 200
if (code === StatusCode.success) {
  console.log("成功")
} else if (code === StatusCode.paramsError) {
  console.log("失败,请求参数问题")
} else if (code === StatusCode.serverError) {
  console.log("失败,服务器问题")
}

枚举小知识:

// 递增
enum Eq{
  a, // 0
  b, // 1
  c  // 2
}

// 默认从0开始递增,有值则在原有基础上递增
enum Lz{
  d, // 0
  e: 6, // 6
  f // 7
}

泛型的基本用法

泛型:类型参数化

没写泛型之前,定义函数是这样的

function fn(n:number):number {
 return n
}
fn(100)
fn(true) // 报错

image.png

若使fn(true)不报错,可以这样做:

function fn(n:number|boolean):number|boolean {
 return n
}
fn(100)
fn(true)
// fn('') 报错

若fn函数想传字符串,依然会报错,还得再声明类型,而泛型简化了该流程。

function fn<T>(n: T):T {
  return n
}

// <boolean>,boolean是实参,T是形参,boolean传给T
fn<boolean>(true) // √
fn<string>('') // √
fn<'haha'>('haha') // √ 值就是类型
fn<number|string>(2) // √

泛型参数可以传多个,比较灵活

function fn<T, K>(n: T, m: K):T {
  return n
}

fn<boolean, number>(true, 1) // √
fn<string, symbol>('', Symbol(1)) // √
fn<'haha', null>('haha', null) // √ 值就是类型
fn<number|string, (name:string) => void>(2, function(x){
  console.log('只要不return都ok,因为返回值是void')
}) // √

image.png

泛型在类型别名和接口上的应用

泛型-类型别名

没有使用泛型之前,类型别名是这么用的

type ObjType = {
  name: string,
  fn: () => boolean
}

let obj:ObjType = {
  name: 'xm',
  fn: () => {
    return true
  }
}

泛型 + 类型别名

type ObjType<T, G> = {
  name: T,
  fn: () => G
}

// 类型传给T,G
let obj:ObjType<string, boolean> = {
  name: 'xm',
  fn: () => {
    return true
  }
} // √

改造一下:

type StrOrNum = string | number

type ObjType<T, G> = {
  name: T,
  fn: () => G
}

// 泛型中<>也可以使用类型别名(StrOrNum)
let obj:ObjType<StrOrNum, boolean> = {
  name: 'xm',
  fn: () => {
    return true
  }
} // √

泛型-接口

没有使用泛型之前,接口是这么用的

interface ObjItf{
  name: string,
  arr: Array<number|string|boolean>,
  fn: () => { code: string }
}

let obj:ObjItf = {
  name: 'xm',
  arr: [1, '2', false],
  fn: () => {
    return { code: '1' }
  }
}

泛型 + 接口

interface ObjItf<A, B, C>{
  name: A,
  arr: B,
  fn: C
}

type StrOrNum = string|number
type ArrType = Array<number|string|boolean>
type FnType = () => { code: string }

// 类型传给A.B.C
let obj:ObjItf<StrOrNum,ArrType,FnType> = {
  name: 'xm',
  arr: [1, '2', false],
  fn: () => {
    return { code: '1' }
  }
}

泛型约束-extends

先来看下泛型使用

// 类型别名+泛型
// 给T和G传什么类型都行,T在这里是string
type objType<T,G> = {
  name: T,
  fn: () => G
}

// 我传什么,泛型的类型就是什么
// 泛型可以规定’我们‘一定要传什么类型吗?
let obj:objType<string, boolean> = {
  name: '',
  fn: () => true
}

泛型的出现是为了’类型‘传的更随便,但是可以约束泛型的类型吗?

// 可以的

// 泛型G使用默认值,没给G传类型,就使用默认类型
type StrOrNum = string|number
type objType<T extends StrOrNum, G=number> = {
  name: T,
  fn: () => G
}

// T只能是string或number,因为被extends限制了类型
let obj:objType<string, boolean> = {
  name: '',
  fn: () => true
}

泛型条件分配

例子:

// ③
type Str = string
// ②
type FxType<H> = H extends Str ? string : boolean
// ①
const whatValue:FxType<string> = 'string extends string' // √

改造一下:

type Str = string | number
type FxType<H> = H extends Str ? string : boolean

const whatValue:FxType<string> = 'string extends Str'

image.png 再改造下:

type Str = string
type FxType<H> = H extends Str ? string : boolean

const whatValue:FxType<string | number> = 'what whole type?'

image.png 解释:

  1. 特殊:如果泛型传递的参数是联合类型的话,那么会用其中一种类型一个一个的来与extends右侧进行比对
  2. string extends string,结果是string
  3. number extends string,结果是boolean

eq:将上面的例子拆解,为什么trys的类型变成boolean了?

上面泛型extends比对是特例,下面的结果才是正常思维现象。得出的结果可以这么理解,extends 右边比左边就是牛逼,右边大于等于左边才为真,否则就为假。

image.png

再看个例子:

type Str = string
type StrOrNum = string | number

type JudgeType<T> = 
  T extends Str ? string : 
  T extends StrOrNum ? symbol : boolean

let variable: JudgeType<string | number>

类型是 string|symbol

如何进行完整匹配

加个中括号

type Str = string
type FxType<H> = [H] extends [Str] ? string : boolean

const whatValue:FxType<string | number> = '' // 报错
  1. [string | number] extends [string] ? string : boolean
  2. 类型为boolean

image.png

extends的条件判断

类型判断

type A = {
  name: string,
  age: 3,
  canWan: boolean
}2
type B = {
  name: string
}
// 类型为值类型,为false
type JudgeType = B extends A ? true : false

let val:JudgeType = false // √
  1. 类型对象 extends 类型对象,B extends A
  2. B包含A,并且B中属性多于A,条件判断为真
  3. B中属性只要少于A,就为假

image.png image.png 联合类型判断

type A = string
type B = string | number

const v1: A extends B ? string : boolean = '' // √
const v2: B extends A ? string : boolean = false // √ 

extends的几个意思

B继承A,B包含A所有规范并可以额外指定规范,有谁使用了B这个规范,则要满足B、B extends A的所有规范

type A = { name: '' }
interface B extends A {
  age: number
}
let obj:B = {
  age: 2,
  name: ''
}

看个泛型案例:

目的:extends约束传递给函数的参数

function fn<T extends { id: number, record(n: number): number}>(arr: T){}

fn([]) // √
fn([{}]) // ×,既然定义了{},就要规范完整
fn([{ id: 1, record(){ return 1 }}]) // √
fn([{ id: 1, record(n){ return n }}]) // √
  1. arr:T[],参数是数组,没有返回值,问题来了,T是什么?
  2. 为了搞清楚T是什么,有必要看段代码理解一下
// 声明常量arr,是个数组[],数组内值的类型是number
const arr:number[] = [1,2,3]
// 拿这个fn传的参数举例
fn([{ id: 1, record(n){ return n } }])
// T就是{ id: 1, record(n){ return n } }类型
  1. fn([{ }]) 报错的原因就是T中必须要有id和record

image.png

类的定义(类-接口)

image.png image.png

该如何写呢?

image.png

注意点:在定义类的同时,相当于定义了一个同名的接口

class Person{
  age: number
  constructor(age: number) {
    this.age = age
  }
  getSomething () {
    return '随意~'
  }
}
new Person(18)

// Person接口
let obj:Person = {
  age: 1,
  getSomething () {
    return ''
  }
}

该Person类如果用接口的写法该如何写?

// 这里
interface Person{
  age: number
  getSomething: () => string
}

let obj:Person = {
  age: 1,
  getSomething () {
    return ''
  }
}

类的继承

class Person {
  myName: string
  constructor(name: string) {
    this.myName = name
  }
  getName() {
    return this.myName
  }
}

class Male extends Person {
  // 类的属性age规定是number类型
  age: number
  // 参数maleAge规定是number类型
  constructor(maleName: string, maleAge: number) {
    // 相当于调用父类constructor并将参数传入
    super(maleName)
    this.age = maleAge
  }
  // 重写(子类有执行子类,没有则用父类【new Male】)
  getName() {
    return 'I~am ' + this.myName
  }
}

const m = new Male('黄晓明', 32)
console.log(m.myName) // 黄晓明
console.log(m.getName()) // I~am 黄晓明
console.log(m.age) // 32

类的修饰符

  1. public: 默认修饰符,类内外都可访问
  2. private: 私有,只允许本类访问
  3. protected: 受保护的,本类和子类可以访问,类外不行

private image.png

image.png

protected image.png

image.png

类的静态属性

静态属性/方法只能由类调用,类实例访问不到

class Person {
  static text: string = 'hi'
  static getText() {
    return this.text
  }
}

console.log(Person.text)
new Person().text // ×
console.log(Person.getText())
new Person().getText() // ×

image.png

抽象类和接口

抽象类

  1. 抽象类是描述,描述着继承它的类应该具有什么属性、方法
  2. 哪个类继承了抽象类,就得实现抽象类中定义的抽象属性和抽象方法
  3. <用抽象类来规范’继承该抽象类的普通类‘>
abstract class Person {
  abstract name:string
  abstract isTrue(): boolean
  getAge() {
    return 24
  }
}

class Male extends Person {
  name = ''
  isTrue() {
    return true
  }
}

const m = new Male()
console.log(m.getAge) // 24
  1. 抽象类不能被实例化
image.png

不能创建一个抽象类的实例

image.png

接口

// 给类定义了规范
interface clsItf{
  name: string
  getName: () => void
}

// 类去实现了该规范
class Person implements clsItf{
  name=''
  getName(){}
} // √

类型断言 as

TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。

const obj = {};
obj.num = 123; // Error: 'num' 属性不存在于 ‘{}’
obj.str = 'hello'; // Error: 'str' 属性不存在于 '{}'

这里的代码发出了错误警告,因为 obj 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 num 或 str,你可以通过类型断言来避免此问题:

interface OItf {
  num: number;
  str: string;
}

const obj = {} as OItf;
obj.num = 123;
obj.str = 'hello';

通过类型断言,排除自己不需要的

type User = { name: string, age: number, get(n: string): void }

type FilterType <T,U> = {
  [K in keyof T as Exclude<K, U>]: T[K]
}

type ValueType = FilterType<User, 'name'>

image.png 怎么理解?拆解下

① 这相当于什么都没干

image.png ② 加上as image.png

keyof

keyof一般跟在接口后面,keyof 接口 -> 表示它的类型是接口的某一个属性

interface EqItf{
  isHappy: boolean
  yourName: string
  myAge: number
}

type EqType = keyof EqItf

let eq:EqType
eq = 'isHappy' // √
eq = 'yourName' // √
eq = 'myAge' // √
eq = 'isHappya' // ×

image.png

interface EqItf{
  isHappy: boolean
  yourName: string
  myAge: number
  [key1: number]: {} // 属性是number类型的
  [key2: string]: []
}

// 只看EqItf的key
type EqType = keyof EqItf

let eq:EqType
eq = 1 // √
eq = '' // √

扩展:

image.pngimage.png ③ 即使规定了obj和key的类型,但obj[key]依然会报错,为什么? image.png 因为ts认为,这个key不一定是在obj中,所以得换个写法

function getAttribute<T>(obj: T, key: keyof T):T[keyof T] {
  return obj[key]
}

const user = { name: 'lwy', age: 24 }
getAttribute(user, 'age')
  1. 代入,user相当于T
  2. key:keyof T,说明key的值一定是在user中

in

’遍历’

type StrOrNum = string | number

type OType = {
// 对象属性可以是string或number类型
// 属性值为boolean类型
  [key in StrOrNum]: boolean
}

let obj:OType = {
  'ha': true,
  89: false
}
image.png

typeof

提取变量或对象的类型

提取变量

let word = 'nihaots'
type VType = typeof word
let str = '' // √
str = 1 // ×
image.png

image.png

提取对象

let o = {
  age: 2,
  fn: (x:string) => 2
}

type OType = typeof o

let obj:OType = {
  age: 6,
  fn: () => {
    return 7
  }
} // √

obj.fn(2) // ×
image.png

image.png

infer在ts中的使用方法

infer P,表示声明一个待推断的类型变量P

需求:取类型

type TTtype = {
  name: string
  age: number
}
type fetchType<T> = T extends { name:infer M, age:infer M } ? M : T
/**
   T extends { name:infer M, age:infer M } ? M : T
 这句话的意思是:如果T能赋值给{ name:infer M, age:infer M },
 则结果是{ name:infer M, age:infer M }类型中的参数M,否则返回P
*/
let val:fetchType<TTtype>
  1. infer M,相当于是定义一个待推断的类型。这个M是什么类型取决于T中属性参数的类型
  2. 第一个M是string,第二个M是number,所以val就是联合类型:string | number image.png

没用infer可以吗?

不行哈!记住,infer是在声明一个待推断的类型 image.png

两个都是M?写其他的可以吗?

image.png

image.png

image.png 各式各样,都可以

扩展

①通过遍历来获取类型

type User = { name: string, age: number, get(a: string): void }

type GetType<T> = {
  [K in keyof T]: T[K] extends (infer U) ? U : K
}

type valueType = GetType<User>

现在离取类型只差最后一步 image.png 解释:

  1. T是User,keyof User -> "name" | "age" | "get"
  2. [K in keyof T],遍历出"name","age","get"
  3. 推导
GetType<User> = {
// 对于name而言,T[K]是string
 name:T[K] extends (infer U) ? U : K
// 对于age而言,T[K]是number
 age:T[K] extends (infer U) ? U : K
// 对于get而言,T[K]是((a:string)=> void)
 get:T[K] extends (infer U) ? U : K
}
GetType<User> = {
 name:string extends (infer U) ? U : K
 age:number extends (infer U) ? U : K
// 对于get而言,T[K]是
 get:((a:string)=> void)) extends (infer U) ? U : K
}

infer U,是在声明一个待推断的类型变量,变量名为U

  1. 所以,name一定是string,age一定是number,get一定是(a:string)=> void),也就是这样:
image.png

如何取出它的类型呢?完整代码如下

type User = { name: string, age: number, get(a: string): void }

type GetType<T> = {
  [K in keyof T]: T[K] extends (infer U) ? U : K
}[keyof T]

type valueType = GetType<User>

相当于就是一个完整对象+[属性1,属性2,属性3],如何理解?

let obj = { name: '', age: 2 }
// obj['name'] obj['age']
// { name: '', age: 2 }['name']
// { name: '', age: 2 }['age']

② 推断出函数返回值类型

type Fn = (n: string) => number[]

type GetFnReturnValue<T> = T extends ((...args: any) => (infer U)) ? U : T

type valueType = GetFnReturnValue<Fn>
image.png 换汤不换药

image.png

命名空间 namespace

一个变量(a)在不同文件声明,就会报重复声明的错误,命名空间的出现就是解决这个问题,命名空间可以类比为匿名函数。

namespace ObjNs {
  export type NumOrStr = number | string
}

let obj:ObjNs.NumOrStr = 99
  1. 命名空间本质上是一个对象,作用是将一系列相关的全局变量统一起来
  2. 没export导出会报错

image.png

通过值类型过滤掉索引

目的是根据值类型排除掉不想要的,再复制出剩下的重组成新的类型规范

type User = { name: string, age: number, get(n: string): void }

type FilterProperty<T, U> = {
  [K in keyof T]: T[K] extends U ? never : K
}[keyof T]

type ValueType = Pick<User, FilterProperty<User, Function>>

image.png

什么意思?拆解理解下:

① 这段代码的目的是,根据类型排除掉不想要的那一组键值对

type User = { name: string, age: number, get(n: string): void }

type FilterProperty<T, U> = {
  [K in keyof T]: T[K] extends U ? never : K
}

type ValueType = FilterProperty<User, Function>

image.png

never相当于无类型,所以ValueType类型只剩下name和age两组

② 取出根据类型排除后的值(这个值相当于key)

type User = { name: string, age: number, get(n: string): void }

type FilterProperty<T, U> = {
  [K in keyof T]: T[K] extends U ? never : K
}[keyof T]

type ValueType = FilterProperty<User, Function>

image.png

③ 最后就是使用Pick工具类型筛选出自个想要的键值对

type User = { name: string, age: number, get(n: string): void }

type FilterProperty<T, U> = {
  [K in keyof T]: T[K] extends U ? never : K
}[keyof T]

type ValueType = Pick<User, "name" | "age">

泛型工具类型

Partial

所有属性都是可缺省(?:),可写可不写

interface PItf {
  name: string
  age: number
}

let obj: Partial<PItf> = {
  
} // √

image.png

Partial是这么定义的:

type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
  1. 类比,T相当于PItf这个接口,Partial是给对象使用的
  2. keyof T -> keyof PItf -> name 或 age
  3. in就是遍历,[P in keyof T] -> name 或 age
  4. T[P] -> PItf['name'] 或 PItf['age'] -> string | number
type Partial<PItf> = {
  name?: string | undefined
  age?: number | undefined
}

相当于 image.png

Required

所有属性都必填,即使设置为可缺省(?:)

interface PItf {
  name: string
  age: number
  isTs?: true
}

let obj:PItf
obj = {
  name: '',
  age: 24
} // √
image.png

image.png

消除报错

interface PItf {
  name: string
  age: number
  isTs?: true
}

let obj:Required<PItf>
obj = {
  name: '',
  age: 24,
  isTs: true // 只能为true
}

image.png

Required是这么定义的:

type Required<T> = { [P in keyof T]-?: T[P]; }
  1. [P in keyof PItf] -> name 或 age 或 isTs
  2. -? -> 抵消问号
  3. T[P] -> string | number | true

整合:

type Required<PItf> = {
  name: string
  age: number
  isTs: true
}

最终,变成全部必填的了

Readonly

属性都只读,不允许修改

interface RItf {
  teacher: string
  num: number[]
}

let obj:Readonly<RItf> = {
  teacher: 'xc',
  num: [2,8,2,5,6]
}
obj.teacher = '' // ×

image.png

Readonly是这么定义的

type Readonly<T> = { readonly [P in keyof T]: T[P]; }
  1. T -> RItf, keyof T -> teacher 或 num
  2. [P in keyof T] -> teacher 或 num
  3. T[P] -> 对应着就是string,number[]

整合

type Readonly<RItf> = {
  readonly teacher: string
  readonly num: number[]
}

Pick

type-类型别名,interface-接口。都是用于规范类型的。Pick存在的意义是,可以选择性地复制出定义在type或interface中的属性及类型

Pick有两个参数

  1. 选择谁的属性
  2. 选择哪几个属性
// type PType = Pick<A, '属性'>
// 从A中选出哪几个属性构造出新的类型
/**
type Props = {
  title: string
  id: number
  num: Array<number>
}
*/

interface Props{
  title: string
  id: number
  num: Array<number>
}

type PType = Pick<Props, 'title' | 'id'>

image.png

Pick是这么定义的

type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
  1. T -> Props,keyof T -> title 或 id 或 num

  2. K extends keyof T -> 限制了参数的传值,K的值一定是 title 或 id 或 num 或它们的组合(title | id)

  3. [P in K] -> 看Pick的第二个参数传了什么,那么P就是它们的其中之一

  4. T[P] -> 就是属性对应的类型

Record

Record<Keys, Type>

列举出某些属性,把它们规定成一致的类型并构造出给对象使用的规范。

eq:

type RType = Record<'name'|'age'|'height', string>

// 必须要有RType声明的所有属性
let obj:RType = {
  name: '',
  age: '',
  height: ''
}
// type RType = Record<'name'|'age'|'height', string>
// 相当于
type RType = {
  name: string
  age: string
  height: string
}

Record是这么定义的

type Record<K extends string | number | symbol, T> = { [P in K]: T }
  1. K extends string | number | symbol -> 属性必须是string或者number或symbol的其中一种
  2. [P in K] -> string 或 number 或 symbol
  3. T -> 类型,属性值的类型

Extract

从type定义的规范中,保留需要的类型

type A = string | number | boolean

// 我只要number和boolean
const val1:Extract<A, number|boolean> = 2
const val2:Extract<A, number|boolean> = true

Extract是这么定义的

type Extract<T, U> = T extends U ? T : never
  1. 代入下,A就是T,U就是number | boolean
  2. 泛型extends,需要一个个比对
  3. string extends number | boolean,结果为never
  4. number extends number | boolean,结果为number
  5. boolean extends number | boolean,结果为boolean
  6. 综合答案为:never | number | boolean,因为never是所有任意类型的子类型,所以最终答案为number | boolean

Exclude

从type定义的规范中,排除不需要的类型

type Sb = string | boolean
type Sbn = string | boolean | number

// A 中排除 B
let num:Exclude<Sbn, Sb> = 2

Exclude是这么定义的

type Exclude<T, U> = T extends U ? never : T

与Extract恰好相反,排除不需要的。

Omit

根据属性名排除,选出想要的

type Info = { name: string, age: number, city:string }

type valueType = Omit<Info, 'name' | 'age'>

image.png

Omit是这么定义的

type Omit<T, K extends string | number | symbol> = {
  [P in Exclude<keyof T, K>]: T[P]
}
  1. 代入,T是Info,Omit的第二个参数只能是string | number | symbol
  2. Exclude<keyof T, K>,keyof Info -> "name" | "age" | "city",K是'name' | 'age',Exclude是从"name" | "age" | "city"排除"name" | "age",所以结果为"city"
  3. [P in Exclude<keyof T, K>],[P in "city"],结果就是"city"
  4. T[P]就是string
  5. 所以Omit就是根据属性名排除不需要的,留下排除之后的键值对。

手写Omit + 测试

type Info = { name: string, age: number, city:string }

type MyOmit<I,G> = Pick<I, {
  [P in keyof I]: P extends G ? never : P
}[keyof I]> // city

type ValueType = MyOmit<Info, 'name' | 'age'>

image.png

end

微信图片_20230401161843.jpg