typescript基础用法案例总结

895 阅读12分钟

前言🔥

  • typescript为了什么
    • 为了编写代码提供丰富的语法提示
    • 并对代码进行类型检测 从而避免很多线上错误
  • ts是js的超集 遵循最新的ES5/ES6规范。
  • ts扩展了js语法。
  • 以下是总结的typescript一些基础用法

基础类型

1.数字 布尔 字符串

// 大写与小写的区别 如下
// number 和 大Number的区别
// Number范围 比 number大
// number是基元 但Number是包装器对象
let num1: number = 1
let num2: Number = 1              // 一切皆为对象 √
let num3: number = Number('1')
let num4: Number = new Number(1)
let num4: number = new Number(1)  // 提示类型错误 ×

// 基本用法
let num: number = 1
let str: string = 'Tom'
let bool: boolean = true

2.数组

// 数组中只能是数字
const arr1: number[] = []

// 并集 既可以是数字 又可以是字符
const arr2: (number | string)[] = ['a', 1]

// 不限制类型 
const arr3: any[] = ['a', 1, {}]

// 泛型方式来声明
const arr4: Array<boolean> = [true, false]
const arr4: Array<number | string> = [1, '2', 3]

3.元组

// 限制长度个数 类型一一对应
// 初始化 类型一一对应
const tuple: [string, boolean, number] = ['a', true, 1]

// 可以通过方法放入 但是只能增加元组中存放的类型
tuple.push('a', 1, true)  // √ 
tuple.push('a', 1, {})    // x 
tuple[3] = 100            // x 不能通过索引更改元组

// 可以通过方法删除
const r = tuple.pop()

4.枚举

// 大写是规范

// 普通枚举
// 枚举可以支持反举(编译时又反向赋值)
// 但是限于索引(数字) 会根据上一个人的值 进行自动的推断
enum TOM {
  NAME,
  AGE,
  HEIGHT = 10,
  BRPTHER,
  ADDRESS
}
console.log(TOM[0])         // √ NAME
console.log(TOM.NAME)       // √ 0
console.log(TOM.AGE)        // √ 1
console.log(TOM.HEIGHT)     // √ 10
console.log(TOM.BRPTHER)    // √ 11
console.log(TOM[NAME])      // × 不能用括号寻找 

// 异构枚举
// 不支持反举(编译时不反向赋值)
enum BOB {
  NAME = 'name',
  AGE  = 'age',
  HEIGHT  = 'height',
  BRPTHER = 'brother',
  // 编译: NUMBER = 10 -> BOB[BOB["NUMBER"] = 10] = "NUMBER"
  NUMBER = 10,
  ADDRESS = 'address'
}

console.log(BOB.NAME)                 // √ name
console.log(BOB['name'])              // ×
console.log(BOB.NUMBER, BOB[10])      // √ 10 NUMBER

// 常量枚举
// 不编译 在使用时给出结果
// 所以有索引 也不会反举
const enum MAY {
  NAME,
  AGE,
  HEIGHT
}

console.log(MAY.NAME) // √ 0  编译 -> console.log(0 /* NAME */)
console.log(MAY[0])   // ×

5.any

// 随意
const arr:any = [ 'Tom', true, {age: '30'} ]

6.null undefined

// 是任何类型的子类型
// 但是在严格模式下
// undefined -> undefined 
// null -> null

// "strict": true
let u: undefined = undefined
let n: null = null
let n2: string = null // x

// "strict": false
let n2: string = null // √

7.never

// 是任何类型的子类型
// 代表不会出现的值
// 如 死循环 走不到的判断 出错

function loop(): never {
  while (true) {}
}

function fn(x:number | string) {
  if (typeof x == 'number') {

  } else if (typeof x === 'string') {

  }else{
    console.log(x) // never
  }
}

function error(message: string): never {
  throw new Error("err")
}

8.void

// 表示函数返回值的
// 也可以描述变量 void的值只能赋予null和undefined
// 严格模式下 不能把null 赋予给void类型

function fn1(): void { console.log('void') }

// "strict": true
function fn1(): void { return undefined }  // √
function fn2(): void { return null      }  // x

// "strict": false
function fn2(): void { return null      }  // √

// "strict": true
let v: void = undefined     // √
let v: void = null          // x

// "strict": false
let v: void = undefined     // √
let v: void = null          // √

9.object

// 表示非原始类型

const create = (obj: object): void => {}
create({})            // √
create([])            // √
create(function(){})  // √

create('string')      // x

10.Symbol BigInt

// Symbol
// 表示独一无二
let s1: symbol = Symbol(1)
let s2: symbol = Symbol(2)

console.log(s1 === s2)      // 不相等

// BigInt
// number类型和bigInt类型是不兼容的
let max = Number.MAX_SAFE_INTEGER
let b1:bigint = BigInt(max)

console.log(max + b1)               // x
console.log(BigInt(max) + b1)       // √

console.log(BigInt(max) + 1)        // x
console.log(BigInt(max) + '1')      // √

console.log(BigInt(max) + BigInt(1) === BigInt(max) + BigInt(2)) // false

类型推导

1.类型推导

// 声明变量没有赋予值时 默认变量是any类型
let name1;

// 声明变量赋值时 则以赋值类型为准
// 可以根据上下文自动推断对应类型
let name2 = 'zhufeng'   // string
name2 = 30              // x

2.联合类型

// 默认联合类型 在没有确定类型之前 
// numOrStr. 只能调用两个类型共同的方法
let numOrStr: string | number;

// 在变量确定类型后 可以设置对应的方法
numOrStr = 'a'    // numOrStr.字符方法提示
numOrStr = 1      // numOrStr.数字方法提示

3.断言

非空断言(!)

// 在不为null 或者 undefined的情况下, 一定有值
// 就是排除null 和 undefined
const el: HTMLElement | null | undefined = document.getElementById('app')
el!.innerHTML = 'el-app'

// 平时写存在null和undefined变量时 会自动加上?. 这个是js特有 
// 编译 el?.innerHTML -> el && el.innerHTML

类型断言(as, <>)

// 第一种写法 值 as 类型
let a1: string | number | undefined;
// a1.lenght 报错错 因为不是这三个共同方法
(a1 as string).length

// 第二种写法 <类型>值
// 因为和jsx冲突 所以不建议使用 (jsx 语法 <div class={}>)
// 尽量使用第一种类型断言因为在react中第二种方式会被认为是jsx语法
(<string>a1).length

双重断言

// 尽量不要使用双重断言 会破坏原有类型关系
// 断言为any是因为any类型可以被赋值给其他类型
let name3: string | number;
let x = (name3! as any) as boolean;
x = true

4.字面量

// 只能采用这几个值(限定值)
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
let direction:Direction = 'Up'

// 联合类型
type IObj = {name:string, age:number} | string

函数类型

1.声明方式

// 第一种
// function 来声明
function sum(a: string, b: string):string {
  return a + b
}
sum('a', 'b')


// 第二种
// 表达式来声明
// 自动根据等号右边的内容 推断左边的类型
// 指定类型 赋予一个可以兼容这个类型的函数
type Sum2 = (a1: number, b1: number) => number;
let sum2: Sum2 = (x, y) => {
  return x + y
}
sum2(1, 2)
// 可以像上面简写(自动推断)
// 也可像向下面写全
// let sum2: Sum2 = (a1: number, b1: number): number => {
//   return a1 + b1
// }

2.可选参数 剩余参数 默认参数

  • 可选参数必须在其他参数的最后面
  • 默认值和可选参数不能一起使用
let sum3 = (a: string, b?: string):string => {
  return a + b
}
sum3('a')     // aundefined


// 可以使用剩余运算符
const sum4 = (x: number, y?: number, ...args: number[]): number => {
  return x + (y as number)
  // return x + y!
}
sum4(1)       // NaN
sum4(1, 2)    // 3

// 默认参数
let sum5 = (a: string, b: string = 'b'): string => {
  return a + b
}
sum5('a')    // ab

// 默认值和可选参数不能一起使用
// 报错(参数不能包含问号和初始化表达式)
// 如下 错误写法
// let sum6 = (a: string, b?: string = 'b'): string => {
//   return a + b
// }

3.函数重载

// 根据传入不同类型的数据 返回不同的结果
// 如下例子

// r1输入的是string类型
// r1输出的提示 let r1: number[] | string[]
// ri期望的提示 let r1: string[]
function toArray1(value:string | number) {
  if(typeof value == 'string'){
    return value.split('')
  }else{
    return value.toString().split('').map(item=>Number(item))
  }
}
let r1 = toArray1('abc')

// 正确写法
// 重载方法写在真实方法的上面
// 函数名要一致
function toArray2(value:string):string[]
function toArray2(value:number):number[]
function toArray2(value:string | number) {
  if(typeof value == 'string'){
    return value.split('')
  }else{
    return value.toString().split('').map(item=>Number(item))
  }
}

// 提示 let r2: string[]
let r2 = toArray2('abc')

class类

1.定义

// 实例上的属性必须先声明
// 构造函数中的参数可以使用可选参数和剩余参数

class Pointer{
  x!:number;    // 定义this.x类型
  y!:number;    // 定义this.y类型
  // z:number;  // 未用! 报错(属性z没有初始化表达式 且未在构造函数中明确赋值)
  constructor(x: number, y?:number, ...args:number[]){
      this.x = x

      // this.y没有undefined类型 所以断言
      this.y = y as number
      // this.y = y!
  }
}

let p1 = new Pointer(100, 100)

2.修饰符

  • public 谁都可以访问到
  • protected 只有自己和自己的后代 能访问
  • private 就是只有自己能访问的属性
  • readonly 仅读 (与const类似) 初始化完毕后不能在修改了 但是对象可以更改属性
// public
// 谁都可以访问到
// 不写public默认也是公开的
class Animal {
    public name!: string; // public 相当于 this.name;
    public age!: number;
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}
class Cat extends Animal {
  constructor(name: string, age: number) {
    super(name, age)

    // 子类访问
    console.log(this.name, this.age)
  }
}
let p2 = new Cat('Tom', 30)

// 外层访问
console.log(p2.name, p2.age) 

// 可以简化父类写法 如下
// class Animal {
//   constructor(public name: string, public age: number) {
//     this.name = name;
//     this.age = age;
//   }
// }
// protected 
// 自己和子类可以访问到
class Animal3 {
  constructor(protected name: string, protected age: number) {
    this.name = name
    this.age = age
  }
}
class Cat3 extends Animal3 {
  constructor(name: string, age: number) {
    super(name, age)

    // 子类可以访问到
    console.log(this.name, this.age)
  }
}
let p3 = new Cat3('Tom', 30)

// 外层无法访问
console.log(p3.name, p3.age)
// private
// 除了自己 谁都访问不到
class Animal4 {
  constructor(private name: string, private age: number) {
    this.name = name
    this.age = age
  }
}
class Cat4 extends Animal4 {
  constructor(name: string, age: number) {
    super(name, age)

    // 子类无法访问
    console.log(this.name, this.age)
  }
}
let p4 = new Cat4('Tom', 30); 

// 外层无法访问
console.log(p4.name, p4.age)
// readonly
// 仅读修饰符
// 初始化完毕后不能在修改了 但是对象可以更改属性
class Animal5 {
  public readonly n: number = 1
  constructor(public readonly name: string, public age: number) {
      this.name = name
      this.age = age
  }

  // changeName(name:string){
  //    仅读属性只能在constructor中被赋值
  //    this.name = name
  // }

  getN() {
    console.log(this.n)
  }
}
class Cat5 extends Animal5 {
  constructor(name: string, age: number) {
      super(name, age)
  }
}
let p5 = new Cat5('Tom', 30)

// x
// p5.changeName('Bob')

// √ 1
p5.getN()

3.静态属性和方法, 以及属性访问器(非ts)

  • static, set, get
  • 静态属性, es7语法, 通过类访问
  • 静态方法, es6语法, 通过类访问
  • 属性访问, es6语法, 通过实例访问
class Animal6 {
  // es7语法 ts不建议使用 会作为实例方法
  // a = 1

  // 静态属性
  static type = '哺乳动物'

  // 静态方法
  static getName() {
    return '动物'
  }
  private _name: string = 'Tom'

  // 属性访问器
  get name() {
    return this._name
  }
  set name(name: string) {
    this._name = name
  }
}

// 静态属性和静态方法是可以被子类所继承的
class Cat6 extends Animal6 {
  constructor() {
    super()

    console.log(Cat6.type)
  }
}

let p6 = new Animal6()

// Tom 动物 哺乳动物
console.log(p6.name, Animal6.getName(), Animal6.type)

let c6 = new Cat6()

// 动物
console.log(Cat6.getName())

4.super属性(非ts)

  • super
  • 子类继承父类, 在使用父类属性方法时, 必须在子类constructor第一行调用super()
class Animal {
  say(message:string) { console.log(message) }

  static getType() { return '动物' }
}

class Cat extends Animal {
  constructor() {
    super()
  }

  // 原型方法中的super指代的是父类的原型
  say() { super.say('猫咪') }

  // 静态方法中的super指代的是父类
  static getType(){ return super.getType() }

}

let cat = new Cat()

// 动物 猫咪
console.log(Cat.getType(), cat.say())

5.类的装饰器

  • es7语法
  • 干什么用的
    • 可以给类扩展功能 扩展类中的属性和方法
    • 也可以复用扩展功能
    • 不能修饰函数 函数会有变量提升的问题
  • 前提说明
    • 装饰器 目前仍是一个实验性语法
    • 需要开启(ts配置文件) experimentalDecorators:true
    • 装饰器要挨着修饰的类 或者类的方法 或者类的属性(中间空格无所谓, 主要是不要有其他无关代码)

执行顺序

// 洋葱模型
// 装饰器的执行顺序

// target -> Person类
function addSay1(val: string) {
  console.log(val)
  return function (target: any) { console.log(1) }
}

function addSay2(val: string) {
  console.log(val)
  return function (target: any) { console.log(2) }
}

function addSay3(val: string) {
  console.log(val)
  return function (target: any) { console.log(3) }
}

@addSay1('a1')
@addSay2('a2')
@addSay3('a3')
class Person {}

// 执行顺序
// 首先 a1 -> a2 -> a3
// 然后 3  -> 2  -> 1
let person = new Person()

装饰类

@log
class MyClass {
  // ts要先声明
  // !是非空断言
  logger!: Function
  // logger!: ()=>void
  
}

// target -> MyClass类
function log(target:any) {
  target.prototype.logger = () => `${target.name} 被调用`
}

const t1 = new MyClass()

// MyClass 被调用
console.log(t1.logger())

装饰类中属性

// public

// target -> Person.prototype
// 并在原型上去劫持去赋值一个劫持的name
// 已获取 就触发get
function toUpperCase(target:any, key:string){
  let value = target[key]
  Object.defineProperty(target, key, {
      get() {
        return value.toUpperCase()
      },
      set(newValue) {
        value = newValue
      }
  })
}

class Person {
  @toUpperCase
  public name: string = 'tom'

}

let p1 = new Person()

// TOM
console.log(p1.name)


// 在constructor如何写
// class Person {
//   @toUpperCase
//   public name: string;
//   constructor(name:string) {    
//     this.name = name
//   }
// }
// let p1 = new Person('tom')

// static

// target -> Person类
function double(target: any, key: string) {
  let value = target[key]
  Object.defineProperty(target, key, {
      get() {
          return value * 2
      },
      set(newValue) {value = newValue}
  })
}
class Person {
  @double
  static age: number = 10
}

let person = new Person()

// 20
console.log(Person.age)

装饰类的方法

// target -> Person.prototype
// descriptor 属性描述
function noEnum(target:any, key:string, descriptor:PropertyDescriptor){
  descriptor.enumerable = false
}
class Person {
  // 默认放在实例上
  name = 'bob'

  @noEnum
  // 让 getName 不可枚举
  getName() { return 'Tom' }
}

let person = new Person()

// 循环出来的只有name
for (const key in person) {
  console.log(key)
}

装饰类的参数

function addPrefix(target:any, key:string, paramIndex:number){
  // Person.prototype getName 0 
  console.log(target, key, paramIndex)
}
class Person {
  getName(@addPrefix prefix:string) {
    return prefix
  }
}

let p1 = new Person()

// tom
console.log(p1.getName('tom'))

抽象类

  • 抽象类无法被实例化 只能被继承
  • 抽象方法不能在抽象类中实现
  • 只能在抽象类的具体子类中实现 而且必须实现
abstract class Animal{
  name!:string;
  // 定义返回值类型为 void 时
  // 表示不关心子类返回值类型
  abstract speak():void
}
class Cat extends Animal {
  speak(): string {
    console.log('猫')
    return '123456'
  }
}

let c = new Cat()

// 猫 123456
console.log(c.speak())

接口(interface)

  • interfacetype 的区别
  • interface 可以被类实现和继承 但是type没有
  • type 可以使用联合类型 interface不能使用联合类型

1.接口的定义

interface IObj {
  name: string // 可以加, 或者; 或者什么都不加
  age: number
}

const getObj = (obj: IObj) => { console.log(obj.name, obj.age) }
getObj({ name: 'tom', age: 30 })

// type 联合类型 如下例子
// 如既可以是对象 又可以是字符
// type IObj = {name:string,age:number} | string

2.描述函数

// type ISum = (a: number, b: number) => number
interface ISum {
  (a: number, b: number): number
}

const sum:ISum = (a, b) => { return a + b }

3.描述函数与自定义属性(混合类型)

interface IFnCount {
  (): number,
  count: number
}

const fnCount:IFnCount = () => { return ++fnCount.count }
fnCount.count = 0

4.对象(接口的特性)

  • 接口属性少, 而真实的属性多 解决方案

as 断言

// 保证接口interface中限制的数据类型 必须要有
// 但是调用tomato.时没有size提示
interface IVegetables {
  color: string;
  taste: string;
}

const tomato: IVegetables = {
  color: 'red',
  taste: 'sweet',
  size: 'small'
} as IVegetables

接口合并

// 接口同名 会合并
// 但是会改变原有的接口
interface IVegetables2 {
  color: string;
  taste: string;
}
interface IVegetables2 {
  size:string
}
const tomato2:IVegetables2 = {
  color:'red',
  taste:'sweet',
  size:'big'
}

接口继承

interface IVegetables3 {
  color: string;
  taste: string;
}

interface ITomato3 extends IVegetables3 {
  size: string
}

const tomato3:ITomato3 = {
  color:'red',
  taste:'sweet',
  size:'big'
}

可选属性(?:)

// 调用tomato4.时 可选属性没有提示
interface IVegetables4 {
  color: string,
  taste: string,
  // 任意接口 number symbol可以作为key 会转为字符
  [key: string]: any
  // size: string,
  // id: number
}

const tomato4:IVegetables4 = {
  color:'red',
  taste:'sweet',
  size:'big',
  id: 4,
  [Symbol()]: 'tomato4'
}

索引接口

interface IArr {
  [key: number]: number
}

let arr1: IArr = [ 1, 2, 3 ]
let arr2: IArr = { 1: 1, 2: 2, 3: 3 }

5.如何获取接口中的类型

// 只能通过[]获取
type TA = {key: string, vlaue: string}
interface ITreeItem {
  items: TA[]
}

interface ITree {
  name: string,
  sub: ITreeItem
}

type GetSub = ITree['sub']['items']

6.描述类(implements)

// implements 关键字
interface ISpeakable {
  name: string,

  // 用接口来形容类 void表示不关心返回值
  // 描述当前实例上的方法 或者原型的方法
  speak(): void
}

interface IChineseSpeakable {
  speakChinese(): void
}

class Speak implements ISpeakable, IChineseSpeakable {
  name!: string
  speak(): void {
    throw new Error("Method not implemented.")
  }
  speakChinese(): string {
    return ''
  }
  
}

7.描述类实例

interface IClazz {
  // new() 描述的是类 Person代表类的实例 
  new(name: string): Person
}

function createInstance(clazz: IClazz, name: string) {
  return new clazz(name)
}

class Person {
  constructor(public name: string) {}
  eat() { console.log('Person ~ eat') }
}

let p = createInstance(Person, 'tom')
// 如果向createInstance传递其他类怎么办 如下
// 将泛型传到接口里
interface IClazz2<T> {
  new(name: string): T
}

function createInstance2<T>(clazz: IClazz2<T>, name: string) {
  return new clazz(name)
}

class Person2 {
  constructor(public name: string) {}
  eat() {}
}

class Animal2 {
  constructor(public name: string) { }
  drink() {}
}

let pOrA = createInstance2<Animal2>(Animal2, 'tom')

泛型

  • 泛型的用处在于 一开始不知道类型 只有当我们调用的时候 确定类型
  • 类型不确定 只有在执行的时候才能确定

1.单个泛型

const getArr = <T>(len: number, val: T): T[] => {
    let res:T[] = []
    for (let i = 0; i < len; i++) {
        res.push(val)
    }
    return res
}
// 自行推断
// 4 -> T -> number
let arr = getArr(3, 4)

2.多个泛型

function swap<T, K>(tuple: [T, K]): [K, T] {
    return [tuple[1], tuple[0]]
}
// 'a' -> T -> string
//  1  -> K -> number
console.log(swap(['a', 1]))
// 不用自行推断 直接定义
// console.log(swap<string, number>(['a', 1]))

3.约束对象

  • extends 关键字

必须包含某些属性

// 1
interface IWithLength {
    length: number
}
const computeArrayLength= <T extends IWithLength, K extends IWithLength>(arr1:T, arr2:K):number => {
    return arr1.length + arr2.length
}

computeArrayLength('123', {length:3})

// 2
interface IWithLength2 {
    size: string
}
const computeArrayLength2= <T extends IWithLength2, K extends IWithLength2>(obj1:T, obj2:K):string =>{
    return obj1.size + obj2.size
}

console.log(computeArrayLength2({ size: 'a' }, { size: 'b' }))

// 3
const sum = <T extends string>(a: T, b: T): T => { // 约束对象
    return (a + b) as T
}
sum('a','v')

返回指定的类型

// 参数key必须包含在obj中
const getVal = <T extends object, K extends keyof T>(obj:T, key:K): T[K] => {
  return obj[key]
}

console.log('keyof', getVal({a:1, b:2}, 'a'))

// keyof 关键字使用
// 就是 type getObjKeys = "a" | "b"
type getObjKeys = keyof {a:1, b:2}
let objKey: getObjKeys = 'b'

// 就是 type getAnyKeys = string | number | symbol
type getAnyKeys = keyof any

4.类型别名

// 可以使用类型别名,但是类型别名不能被继承和实现。一般联合类型可以使用类型别名来声明
// 能使用interface尽量使用interface

// type IArray = <A, B>(tuple: [A, B]) => [B, A];
interface IArray{
    <T,K>(typle:[T,K]):[K,T]
}
const getArray:IArray = <T, K>(tuple: [T, K]): [K, T] => {
    return [tuple[1], tuple[0]]
}
// const getArray:IArray = (tuple) => {
//     return [tuple[1], tuple[0]]
// }

let r = getArray([3,'a'])

5.默认泛型

interface IDefault<T=string> {
    name: T
}
let name1:IDefault = { name: 'tom' }

type IDefault2 = IDefault<number>
let name2:IDefault2 = { name: 123 }

类型保护

  • 靠的是js特性和ts自带的功能
  • js 通过判断识别所执行的代码块 自动识别变量属性和方法

1.typeof (js特性)

function typeofFn(val: string | number) {
  // 通过js判断 (val.)类型提示不同
  if (typeof val == 'string') {
    val.split
  } else {
    val.toFixed
  }
}

2.instanceof (js特性)

class Person {
  speak() {}
}
class Animal {
  noSpeak() {}
}

interface IClazz {
  new (): Person | Animal
}
// type IClazz = new() => Person | Animal 

const createClass = (clazz: IClazz) => {
  return new clazz
}

let pOrA = createClass(Person)

// let pOrA: Person | Animal
//直接 pOrA.是没有类型提示的
if (pOrA instanceof Person) {
  pOrA.speak
} else {
  pOrA.noSpeak
}

3.in (js特性)

interface Fish {
  swiming: string,
}
interface Bird {
  fly: string,
  leg: number
}

function getTypeIn(animal: Fish | Bird) {
  if ('swiming' in animal) {
    animal // Fish提示
  } else {
    animal // Bird提示
  }
}

getTypeIn({swiming: 'swiming'})

4.is 自定义类型保护(ts特有)

// 可以用来判断一个变量属于某个interface|type
interface Fish2 {
  swiming: string,
}
interface Bird2 {
  fly: string,
  leg: number
}

// 返回true则断定参数animal为Fish2类型
function isFish2(animal: Fish2 | Bird2): animal is Fish2 {
  return 'swiming' in animal
}

function getTypeIn2(animal: Fish2 | Bird2) {
  if (isFish2(animal)) {
    animal // Fish2
  } else {
    animal // Bird2
  }
}

5.null保护

// 要注意的是ts无法检测内部函数变量类型
const addPrefix = (num: number | null) => {
  num = num || 30
  function prefix(fix: string) {
      if (num) {
        return fix + num.toFixed
      }
      // 也可以
      // return fix + num?.toFixed
  }
  return prefix('tom')
}

6.完整性保护

// 主要靠的是never 利用never无法到达最终结果的特性 来保证代码的完整
interface ISquare {
  kind: 'square',
  width: number
}
interface IRant {
  kind: 'rant',
  width: number,
  height: number
}
interface ICircle {
  kind: 'circle',
  r: number
}

const assert = (obj: never) => { throw new Error("err") }

// 完整性保护 保证代码逻辑全部覆盖到
function getArea(obj: ISquare | IRant | ICircle) {
  switch (obj.kind) {
    case 'square':
      return obj.width * obj.width
    case 'rant':
      return obj.width * obj.height
    case 'circle':
      return 3.14 * obj.r ** 2
    default:
      assert(obj) // 必须实现所有逻辑
  }
}

getArea({ kind: 'circle', r: 10 })

交叉类型

  • 是将多个类型合并为一个类型

1.定义

// 例如 有两拨人 一拨人都很帅 另一拨人很高 
// 我们希望找到他们的交叉部分 -> 又高又帅的人
interface Person1 {
  handsome: string,
}
interface Person2 {
  high: string,
}

type P12 = Person1 & Person2

let p12:P12 = { handsome: '帅', high: '高' } 

// 在原有的类型基础上想去扩展属性 可以用交叉类型
type Person3 = Person2 & { money: string }
let p123: Person3 = {
  ...p12,
  money: '钱'
}

2.冲突(never)

// 属性类型不一致 never 
interface IPerson1 {
  name:string,
  age:number
}

interface IPerson2 {
  name:number
  age:number
}
// name 两个属性之间 string & number的值为never
type person = IPerson1 & IPerson2
// name 为 never 报错
// let person:person = {name: '132456', age: 30}

3.函数

// 真正合并属性的时候 要以一方为基础 不会直接相交 有可能会导致never情况
function mixin<T extends object, K extends object>(a: T, b: K): T & K {
  return {...a, ...b}
}
const x = mixin({}, {name: 'tom', age: 30 })

兼容性

  • 主要看结构是否兼容
  • 核心是考虑安全性
  • 从安全角度出发

1.普通类型

// 1.大类型(父) = 小类型(子)
let strOrNum:string | number
let num!:number
strOrNum = num
// 2.检测方式(鸭子检测)
// 只要叫声像鸭子 就是鸭子
type IToStr = {
  toString(): string,
  length: number
}

// 字符串中具备toString()方法 length属性 所以可以进行兼容
// 少的条件 = 多的条件
let name: IToStr = 'tom'

2.接口兼容性

// 接口的兼容性 只要满足接口中所需要的类型即可
// 通过接口的兼容性 可以处理赋予多的属性
interface IVegetables {
  color: string,
  taste: string
}
interface ITomato{
  color: string,
  taste: string,
  size: string
}

let vegetables: IVegetables;
let tomato:ITomato = {
  color: 'red',
  taste: 'sour',
  size: 'big'
}

// 将一个值赋予给了类型, 是不会出现兼容性的 要求必须满足这个接口
// 但两个接口之间是存在兼容性问题的
// 少的属性 = 多的属性
vegetables = tomato

3.函数兼容性

// 函数的兼容性主要是比较参数和返回值

// 例子1 针对参数的个数 和 返回值类型
let sum1 = (a: string, b: string): string => a + b
let sum2 = (a: string): string => a
// 前提sum2参数类型要与sum1的类型一致
// 参数要少于或等于被赋值的函数(兼容)
sum1 = sum2
// 1
// 当你使用时 sum1的类型 还是一开始定义好的ts类型(一切为了安全)
console.log(sum1('1', '2'))

// 例子2
function forEach<T>(arr: T[], cb: (item: T, index: number, arr: T[]) => void) {
  for (let i = 0; i < arr.length; i++) {
    // 调用时 定好的ts参数 必须传递
    cb(arr[i], i, arr)
  }
}
// 多参数 = 少参数 (ts类型定义 兼容)
// cb: (item: T, index: number, arr: T[]) = (item)
// log: 1 2 3 4
forEach([1, 2, 3, 4], (item) => {
  console.log(item)
})

// 例子3 返回值
type Sum3 = (a: string, b: string) => { name: string }
type Sum4 = (a: string) => { name: string, age: number }
let sum3!: Sum3;
let sum4!: Sum4;

// 多的要有少的属性
// 返回值 少 = 多
sum3 = sum4

4.函数的逆变和协变

// 函数的参数是逆变的,返回值是协变的 
// 在非严格模式下函数的参数是双向协变的
class Parent {
  money: string = '100000000000'
}
class Child extends Parent {
  house: string = '豪华'
}
class Grandson extends Child {
  eat: string = '美食'
}

// 对于参数而言 儿子可以处理 钱和房子
function getFn(cb: (person: Child) => Child) {
  return cb(new Grandson)
}

// 通过这个案例可以说明 函数参数可以接收父类 返回值可以返回子类
// r {eat: "美食", house: "豪华", money: "100000000000"}
let r = getFn((person: Parent) => new Grandson)

5.联合类型

function getType(cb: (val: string | number) => string | number) {
  cb('123456')
}
getType((val: string|number|boolean): string => {
  return val as string
})

6.泛型

interface IO<T>{}
let obj1:IO<string>;
let obj2!:IO<number>;
obj1 = obj2

7.枚举

// 不同的枚举类型不兼容
enum USER1 {
  role = 1
}
enum USER2 {
  role = 1
}
let user1!:USER1
let user2!:USER2
// x 报错
// user1 = user2

条件类型

  • extends 关键字
  • infer 关键字

基本使用

// 可以使用extends和三元表达式 实现条件判断
// 例子1
interface Fish {
  name: string,
  type: '鱼'
}
interface Bird {
  name: string,
  type: '鸟'
}
interface Swiming {
  swiming: string
}
interface Sky {
  sky: string
}

type ExprType<T> = T extends Fish ? Swiming : Sky
type ResT1 = ExprType<Fish>
let fish:ResT1 = {
  swiming: '游'
}

// 例子2
interface ISchool1 {
    name: string,
    age: number
}
interface ISchool2 {
    age?: number,
    size: string
}

type School<T> = T extends { name: string } ? ISchool1 : ISchool2
type MySchool = School<ISchool2>
let mySchool: MySchool = {
    size: 'size'
}

2.条件类型分发

interface Fish {
  name: string,
  type: '鱼'
}
interface Bird {
  name: string,
  type: '鸟'
}
interface Swiming {
  swiming: string
}
interface Sky {
  sky: string
}

type ExprType<T> = T extends Fish ? Swiming : Sky
// -> ExprType<Fish>
// -> ExprType<Bird>
// -> type ResT2 = Swiming | Sky
// 返回联合类型
type ResT2 = ExprType<Fish|Bird>
let fOrB:ResT2 = {
  // swiming: '游',
  sky: '飞'
}

3.ts内置类型

  • 内部用条件来实现的类型(一部分)

Exclude 排除类型

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

// 分发-> string  extends boolean ? never : string
// 分发-> number  extends boolean ? never : number
// 分发-> boolean extends boolean ? never : boolean
// 分发-> string | number | never
// end-> type MyExclude = string | number
type MyExclude = Exclude<string | number | boolean, boolean>
let myExclude: MyExclude = '123'

Extract 抽取类型

// 同上
type Extract<T, K> = T extends K ? T : never
// type MyExtract = boolean
type MyExtract = Extract<string | number | boolean, boolean>
let myExtract = true

NoNullable 非空检测

type NonNullable<T> = T extends null | undefined ? never : T
// type MyNonNullable = string | number
type MyNonNullable = NonNullable<string | number | null | undefined>
let myNonNullable: MyNonNullable = 123

4.类型推断(infer)

  • ts内置类型
  • infer要配合 extends 否则无法使用
  • infer有推断类型的功能 可以自动推断出结果
  • infer放在哪里 就是推断哪里的结果

ReturnType 返回值类型

function getUser(a: string, b: number) {
    return { name: a, age: b }
}
// 通过函数 直接获取返回值
type ReturnType<T extends ((...args: any[])=> any) > = T extends ((...args: any[]) => infer R) ? R : any
// type MyReturnType = { name: string; age: number; }
type MyReturnType = ReturnType<typeof getUser>
let myReturnType: MyReturnType

// 也可以这样 但要执行函数
// let user = getUser('tom', 30)
// type User = typeof user
// let user2: User

Parameters 参数类型

type Parameters<T extends ((...args: any)=> any)> = T extends ((...args: infer P) => any) ? P : any
// type MyParameters = [a: string, b: number] 元组
type MyParameters =Parameters<typeof getUser>
let myParameters:MyParameters = ['tom', 30]

ConstructorParameters 构造函数参数类型

class Person {
  name: string = 'tom'
  age: number = 30
  constructor(name:string) {}
}
type ConstructorParameters<T extends new (...args: any[])=> any> = T extends new (...args: infer CP)=> any ? CP : any
// type myConstructorParameters = [name: string]
type MyConstructorParameters = ConstructorParameters<typeof Person>
let myConstructorParameters: MyConstructorParameters = ['tom']

InstanceType 实例类型

type InstanceType<T> = T extends { new(...args: any): infer R } ? R : any
type MyInstance = InstanceType<typeof Person>
let myInstance: MyInstance = { name: 'bob', age: 25 }

5.实践

// 1.将元组类型 变成 联合类型
type Tuple = [string, boolean, number]
type ElementOf<T> = T extends Array<infer E> ? E : never
// type TupleToUnion = string | number | boolean
type TupleToUnion = ElementOf<Tuple>

// 2.T1 T2函数转化为交叉类型
type T1 = { name: string }
type T2 = { age: number }
// type ToIntersection<T> = T extends [(x: infer U) => any, (x: infer U) => any] ? U : never
type ToIntersection<T> = T extends Array<(x: infer U) => any> ? U : never
// type T3 = T1 & T2
type T3 = ToIntersection<[(x:T1)=>any, (x:T2)=>any]>
let myT3: T3 = {
    name: 'tom',
    age: 30
}

内置类型

  • 根据定义好的已有的类型 演变出一些其他类型

1.Partial转化为可选属性

interface ICompany {
  name: string,
  address: string,
  size?: string
}
interface IPerson {
  name?: string,
  age: number,
  company?: ICompany
}

// 第一层 IPerson 都为可选了 但ICompany 没有
type Partial<T> = { [K in keyof T]?: T[K] }
// 自己可写一个深度可选
// type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }

type MyPartial = Partial<IPerson>
let myPartial!:MyPartial

2.Required 去掉可选

interface ICompany2 {
  name?: string,
  address?: string,
  size?: string
}

type Required<T> = { [K in keyof T]-?: T[K] }
type MyRequired = Required<ICompany2>
let myRequired!:MyRequired

3.Readonly 仅读属性

interface ICompany3 {
  name?: string,
  address?: string,
  size?: string
}

type Readonly<T> = { readonly [K in keyof T]: T[K] }
type MyReadonly = Readonly<ICompany3>
let myReadonly:MyReadonly = {
  name: 'tom'
}

4.Pick 挑选所需的属性

interface ICompany4 {
  name?: string,
  address?: string,
  size?: string
}
type Pick<T, K extends keyof T> = { [X in K]: T[X] }
type MyPick = Pick<ICompany4, 'name' | 'address'>
let myPick!:MyPick

5.Qmit 忽略属性

interface ICompany5 {
  name: string,
  address: string,
  size: string
}

// 都是ts内置类型
type Exclude<T, K> = T extends K ? never : T
type Qmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type MyOmit = Qmit<ICompany5, 'name'>

6.Record记录类型

// 他会将一个类型的所有属性值都映射到另一个类型上 并创造一个新的类型

// type A = keyof any -> type A = string | number | symbol
type Record<K extends keyof any, T> = { [P in K] : T }

// 例子1
let person: Record<'name' | 'age', any> = { name: 'tom', age: 30 }

// 例子2
function map<K extends string | number | symbol, V, X>(obj: Record<K, V>, cb: (item: V, key: K) => X): Record<K, X> {
  let result = {} as Record<K, X>
  for (const key in obj) {
    result[key] = cb(obj[key], key)
  }

  return result
}

let m = map({name: 'tom', age: 30}, (item) => {
  return '$' + item
})

// 例子3
interface PageInfo {
  title: string;
}
type Page = "home" | "about" | "contact"

const nav: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
}

自定义类型

  • 可以根据内置类型 写几个常见类型

1.裝包与拆包

// { name: 'tom', age: 30 }
// 变化为
// {
//     name: {
//       get() { return },
//       set(newValue) {}
//     },

//     age: {
//       get() {},
//       set(newValue) { return }
//     }
// } 

let data = {
  name: 'tom',
  age: 30
}

// 1.装包
interface Proxy<T> {
  get() : T,
  set(value: any): void
}

type Proxify<T> = {
  [K in keyof T]: Proxy<T[K]>
}

function proxify<T extends object>(obj: T):Proxify<T> {
  let result = {} as Proxify<T>
  for (const key in obj) {
    let value = obj[key]
    result[key] = {
      get () {
        return value
      },
      set(newValue: T[Extract<keyof T, string>]) {
        value = newValue
      }
    }
  }

  return result
}

let proxyData = proxify(data)

// tom -> bob
console.log(proxyData.name.get())

proxyData.name.set('bob')

console.log(proxyData.name.get())

// 2.拆包
function unProxify<T extends object>(obj: Proxify<T>): T {
  let result = {} as T
    for (let key in obj) {
        let value = obj[key]
        result[key] = value.get()
    }
    return result
}

let unProxifyData = unProxify(proxyData)

2.接口取差集, 交集

let person1 = {
    name: 'tom',
    age: 30,
    address: '地球'
}
let person2: {
    address: '地球'
}

// 1.差集 { name: string, age:number }
type Diff<T extends object, K extends object> = Omit<T, keyof K>
type MyDiff = Diff<typeof person1, typeof person2>

// 2.交集 { address: string }
type Inter<T extends object, K extends object> = Pick<K, Extract<keyof T, keyof K>>
type MyInter = Inter<typeof person1, typeof person2>

3.解决& 属性出现never问题

// 期望age 以Person2为准
type Person1 = {
    age: number,
    name: string
}
type Person2 = {
    age: string,
    address: string
}


type Merge<T extends object, K extends object> = Omit<T, keyof K> & K

// 将类型展开方便提示
type Compute<T> = { [K in keyof T]: T[K] }

type MyMerge = Compute<Merge<Person1, Person2>>

unknown

  • unknown 是any的安全类型
// 例子1
// 不能通过属性变量取值 为了安全性
let name: unknown = 1
// 报错
// name.toFixed


// 例子2
// unknown和其他类型在联合类型都是unknown类型
// -> type X = unknown
type X = boolean | unknown


// 例子3
// unknow 和其他类型 交叉类型都是其他类型
// -> type Y = boolean
type Y = boolean & unknown


// 例子4
// keyof any 但是不能用 keyof unknown
// -> type S = never
type S = keyof unknown

命名空间

1.内部模块

  • 使用命名空间来声明 解决同一个文件下的命名冲突问题
  • namespace 关键字
  • module 关键字(最终还是转成namespace 所以建议使用namespace)
  • 命名空间就是通过自执行函数来是实现的

使用

export namespace zoo {
  // 必须有export导出
  export class Dog { eat() { console.log('zoo dog') } }
  export let address = '动物园'
}
export namespace home {
  export class Dog { eat() { console.log('home dog') } }
  export let address = '家里'
}

// 通过命名空间获取zoo
let dog_of_zoo = new zoo.Dog()
dog_of_zoo.eat()
dog_of_zoo.address

let dog_of_home = new home.Dog()
dog_of_home.eat()

嵌套使用

export namespace zoo {
  export class Dog { eat() { console.log('zoo dog') } }
  export namespace bear{
    export const name = '熊'
  }
}

console.log(zoo.bear.name)

特殊情况

// 命名空间名一致会怎么样

// 如下
// 两个重名的命名空间会合并
// 但是重名的会报错
namespace Home {
  // 报错 Dog重名
  // export class Dog{}
  export const a = 'a'
}

// namespace 和 module(最终还是转成namespace)
module Home {
  export class Dog{}
  export const b = 'b'
}

// {} 'a' 'b'
console.log(new Home.Dog, Home.a, Home.b)

2.外部模块

  • export import语法 ESModule
  • require module.exports语法 commonjs
  • cjs模块 ts单独的提供一个导出方式export = / import x = require(''), 因为cjs没有类型提示
  • es6模块 全部用 export , default export {} / import 不变

es6例子

// 在a.ts文件下导出
export default 'tom'
// 在index.ts文件下引入
import name from './a'

// tom
console.log(name)

cjs例子

// export = 'tom'
// index.ts文件
import r  = require('fs')
// r.有提示

导入导出规范

import $ from 'jquery'        // 只适用于 export default $

const $ = require('jquery')   // 没有声明文件可以直接使用 require语法

import * as $ from 'jquery'   // 为了支持 Commonjs规范 和 AMD规范 导出时采用export = jquery

import $ = require('jquery')  // export = jquery 在commonjs规范中使用

类型声明

1.声明全局变量

  • declare 关键字
  • 没有确切的含义 只是为了防止出错
// 那vue项目举例
// 比如你在文件index.html声明了一个变量
// 在其他ts文件拿到 按理说是存在的 现在会报错
// 可以用declare去定义


// 只是去定义变量 或者 ts类型 不是去赋值
// 在你去使用的时候 会去当前文件夹去找 declare
// 为了防止不报错

// 当前文件下 不能存在 import 或者 export
// 因为会是 当前文件变成局部定义 全局下是拿不到的

// a.ts文件
declare let age: number
declare function sum(a: string, b: string): void
declare class Animal { }

declare const enum Seaons{
  Spring,
  Summer,
  Autumn,
  Winter
}
declare interface Person {
  name:string,
  age:number
}

// index.ts文件

let u: number = Seaons.Autumn

// 浏览器报错 sum is not defined
// declare只是定义 为了ts防止报错 不是声明变量
sum('a', 'b')

export {}
// b.ts文件
// 例子 声明jQuery类型
// jquery通过外部CDN方式引入 想在代码中直接使用

// 第一种声明
declare const $:(selector:string)=>{
  height(num?:number):void
  width(num?:number):void
}

$('').height()

// 第二种声明
declare namespace jQuery {
    function ajax(url:string,otpions:object):void;
    namespace fn {
        function extend(obj:object):void
    }
}
jQuery.ajax('/',{})
jQuery.fn.extend({})

2.类型声明文件

  • 类型声明文件以 文件名.d.ts 结尾
  • 默认在项目编译时会查找所有以.d.ts结尾的文件
// jquery.d.ts

// 第一种声明
declare const $:(selector:string)=>{
  height(num?:number):void
  width(num?:number):void
}

// 第二种声明
declare namespace jQuery {
  function ajax(url:string,otpions:object):void
  namespace fn {
    function extend(obj:object):void
  }
}

// 例子 如果文件是以.vue形式
declare module '*.vue' {
  import { defineComponent, App } from 'vue';
  const component: ReturnType<typeof defineComponent>;
  export default component
}

3.编写第三方声明文件

* 配置tsconfig.json
* "moduleResolution": "node",
* "baseUrl": "./",
* "paths": {
*    "*": ["types/*"]
* },
// jquery声明文件
// types/jquery/index.d.ts

declare function jQuery(selector: string): HTMLElement
declare namespace jQuery {
  function ajax(url: string): void
}
export = jQuery

4.第三方声明文件

* @types是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀
* `npm install @types/jquery -S`
* 当使用jquery时默认会查找 node_modules/@types/jquery/index.d.ts 文件

* 查找规范
1. node_modules/jquery/package.json 中的types字段
2. node_modules/jquery/index.d.ts
3. node_modules/@types/jquery/index.d.ts

扩展全局变量类型

1.局部变量

// 不要有import 或者 export

// 1
interface String {
  double():string
}
String.prototype.double = function () {
  return this as string + this
}

let str = 'tom'

// 2
interface Window {
  mynane:string
}

console.log(window.mynane)

2.模块内扩展全局变量

// global 关键字
declare global{
  interface String {
    double():string
  }

  interface Window{
    myname:string
  }
}

export {}