TS-基础篇

239 阅读26分钟

常用类型

string

  • 字符串

  • let str: string = 'hello ts'
    

number

  • 数字

  • let num: number = 100
    

boolean

  • 布尔

  • let bool: boolean = true
    

数组

  • type[]

  • Array 泛型写法

  • let arr: number[] = [1, 2, 3]
    let arr2: Array<number> = [1, 2, 3]
    

any

  • 不希望某个特定值导致类型检查错误
  • 设置为any之后不会进行类型判断,不推荐

变量上的类型注释

基元类型

  • let myName: string = 'zzy' // :string 冒号+类型
    

函数

  • function greet(name: string): void {
      console.log('hello,' + name)
    }
    // : string 参数类型注释 
    // : void 返回值类型注释
    

对象类型

  • function printCoord(pt: { x: number; y: number; z ?: number }) {
      console.log('x坐标为:' + pt.x + ' y坐标为:' + pt.y + ' z坐标为:' + pt.z)
    }
    
    printCoord({ x: 2, y: 3 })
    
    // : { x: number; y: number; z?: number } 参数的类型注释是对象类型 用分号或逗号分割 ?:代表可选参数
    

联合类型

  • let id: number | string
    
    function printId(id: string | number): void {}
    printId(1)
    printId('a')
    // 两个或多个其他类型组成的类型
    

类型别名

  • `type 类型名称` 可以多次复用类型 type定义的是变量的类型,而不是变量
    // 对象类型
    type Point = {
      x: number
      y: number
    }
    
    function printCoord2(pt: Point) {}
    printCoord2({ x: 0, y: 0 })
    
    // 联合类型
    type ID = number | string
    function printId2(id: ID) {}
    printId2(100)
    printId2('hello')
    
    // 基元类型
    type USerName = string
    function userInfo(str: string): USerName {
      return str.slice(0, 2)
    }
    let user = userInfo('hello')
    user = 'newHello'
    

接口

  • interface Locate {
      x: number
      y: number
    }
    
    function getLocation(pt: Locate) {}
    getLocation({
      x: 100,
      y: 100
    })
    
    

接口与类型别名的异同

  • // 1.通过接口定义类型
    interface Animal {
      name: string
    }
    interface Bear extends Animal {
      honey: boolean
    }
    const bear: Bear = {
      name: 'winie',
      honey: true
    }
    console.log(bear.name)
    console.log(bear.honey)
    
    // 2.通过类型别名定义类型
    type Animal2 = {
      name: string
    }
    type Bear2 = Animal2 & {
      honey: boolean
    }
    const bear2: Bear2 = {
      name: 'winie2',
      honey: false
    }
    console.log(bear2.name)
    console.log(bear2.honey)
    
    // 3.通过接口向现有类型添加新字段
    interface MyWindow {
      count: number;
    }
    interface MyWindow {
      title: string;
    }
    const w: MyWindow = {
      title: 'hello ts',
      count: 100
    }
    
    // 4.通过type不可以向现有类型添加新字段
    // 类型创建后不可以更改
    // type MyWindow2 = {
    //   count1: number
    // }
    // type MyWindow2 = {
    //   count: number
    // }
    

类型断言

  • //与类型注释一样,类型断言由编译器删除,不会影响代码运行时的行为
    
    // as 具体类型   放在需要断言语句的后面
    const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement
    // <具体类型>    放在需要断言语句的前面
    const myCanvas = <HTMLCanvasElement>document.getElementById('main_canvas') 
    

文字类型

  • let tsetStr = 'a'
    tsetStr = 'newA'
    
    // const tsetStr2 = 'b'
    // tsetStr2 = 'newB''
    
    let test: 'hello' = 'hello'
    // test = 'world'
    
    // 规定了入参的字符串
    function printText(alignment: 'left' | 'right' | 'center') {}
    printText('left')
    
    // 通过接口实现文字类型组合
    interface Options {
      width: number
    }
    function configure(x: Options | 'auto') {}
    configure({
      width: 100
    })
    configure('auto')
    
    // 布尔文字类型
    let b1: true = true
    let b2: false = false
    
    // 文字推理
    function handleRequest(
      url: string,
      method: 'GET' | 'POST' | 'PUT' | 'DELETE'
    ) {}
    
    // 因为ts默认会将对象类型隐式断言为字符串
    // 方法1:通过给函数入参时将req.method断言为'GET’
    const req = {
      url: 'http://example.com',
      method: 'GET'
    }
    handleRequest(req.url, req.method as 'GET')
    // 方法2:或在定义req.method时将req.method类型断言为'GET'类型
    const req2 = {
      url: 'http://example.com',
      method: 'GET' as 'GET'
    }
    handleRequest(req2.url, req2.method)
    // 方法3:在定义req时将req断言为const
    const req3 = {
      url: 'http://example.com',
      method: 'GET'
    } as const
    handleRequest(req3.url, req3.method)
    
    

null和undefined

  • // null表示不存在
    // undefined表示未初始化的值
    let un: undefined = undefined
    let nu: null = null
    // let st: string = undefined;
    
    function live(x?: number | null) {
      // !.表示绝对不可能是undefined或者null
      console.log(x!.toFixed());
      // ?.表示允许为undefined或者null,即可以不传
      console.log(x?.toFixed());
    }
    

枚举

  • enum Direction {
      Up = 1,
      Down = 2,
      Left,
      Right
    }
    console.log(Direction.Up)
    
  • 编译后的js

  • "use strict";
    var Direction;
    (function (Direction) {
        Direction[Direction["Up"] = 1] = "Up";
        Direction[Direction["Down"] = 2] = "Down";
        Direction[Direction["Left"] = 3] = "Left";
        Direction[Direction["Right"] = 4] = "Right";
    })(Direction || (Direction = {}));
    console.log(Direction.Up);
    

bigint和symbol

  • // bigint:非常大的整数
    // symbol:全局唯一引用
    const bi: bigint = BigInt(100)
    const bi2: bigint = 100n
    
    const sy = Symbol('sym')
    const sy2 = Symbol('sym2')
    
    // console.log(sy === sy2) // 报错
    

类型缩小

  • 宽类型转化为窄类型的过程,常用于处理联合类型

  • function padLeft(padding: number | string,input:string): string {
        // 将特殊的检查称之为类型防护
        // 将类型细化为比声明更具体的类型的过程称之为类型缩小
    	if(typeof padding === 'number') {
            return new Array(padding + 1).join(" ") + input;
        }
        return padding + input
    }
    

typeof 类型守卫

  • function printAll(strs: string | string[] | null) {
      if (typeof strs === 'object' && strs !== null) {
        for (const s of strs) {
          console.log(s)
        }
      } else if (typeof strs === 'string') {
        console.log(strs)
      } else {
        console.log(strs)
      }
    }
    printAll(['a', 'b'])
    printAll('aa')
    printAll(null)
    

真值缩小

  • 条件、 && 、|| 、f语句 、布尔否定(!)
    
    // 0 NaN ""(空字符串) 0n(bigint零) null undefined 会被强制转换为false
    
    // 这两个结果也返回true
    Boolean('hello') // type: boolean, value: true
    !!'world' // type: boolean, value: true
    
    function getUsersOnline(count: number) {
      if (count) {
        return `现在共有${count}人在线!`
      }
      return '现在0人在线. :("'
    }
    
    function printAll2(strs: string | string[] | null) {
      if (strs && typeof strs === 'object') {
        for (const s of strs) {
          console.log(s)
        }
      } else if (typeof strs === 'string') {
        console.log(strs)
      } else {
        console.log(strs)
      }
    }
    printAll2(['a', 'b'])
    printAll2('aa')
    printAll2(null)
    

等值缩小

  • ===、!==、==、!=
    
    function example(x: string | number, y: string | boolean) {
      if (x === y) {
        x.toUpperCase()
        y.toLowerCase()
      } else {
        console.log(x, y);
      }
    }
    

in操作符缩小

  • "value" in x // x具有可选或必须属性的类型的值时才为true
    
    type Fish = {
      swim: () => void
    }
    type Bird = {
      fly: () => void
    }
    type Human = {
      swim?: () => void
      fly?: () => void
    }
    
    function move(animal: Fish | Bird | Human) {
      if ('swim' in animal) {
        return (animal as Fish).swim()
      }
      return (animal as Bird).fly()
    }
    

instanceof操作符缩小

  • x instanceof Foo
    
    function logValue(x: Date | string) {
      if (x instanceof Date) {
        console.log(x.toUTCString())
      } else {
        console.log(x.toUpperCase())
      }
    }
    logValue(new Date())
    logValue('hello ts')
    

分配缩小

  • let x = Math.random() < 0.5 ? 10 : 'hello ts' // 将x的类型设置为 string | number
    x = 1
    x = 'world'
    

控制流分析

  • function example() {
      let x: string | number | boolean
      x = Math.random() < 0.5 // x为boolean类型
      if (Math.random() < 0.5) {
        x = 'hello' // x为string类型
      } else {
        x = 100 // x为number类型
      }
      return x // x为string | number
    }
    

使用类型谓词

  • type Fish1 = {
      name: string
      swim: () => void
    }
    
    type Bird1 = {
      name: string
      fly: () => void
    }
    
    function isFish(pet: Fish1 | Bird1): pet is Fish1 {
      // 返回为true则代表返回类型为Fish1
      // 判断pet中是否含有swim属性
      return (pet as Fish1).swim !== undefined
    }
    
    function getSmallPet(): Fish1 | Bird1 {
      {
        let fish: Fish1 = {
          name: 'sharkey',
          swim: () => {}
        }
    
        let bird: Bird1 = {
          name: 'sparrow',
          fly: () => {}
        }
    
        return true ? bird : fish
      }
    }
    
    let pet = getSmallPet()
    if (isFish(pet)) {
      pet.swim()
    } else {
      pet.fly()
    }
    
    const zoo: (Fish1 | Bird1)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
    
    const underWater: Fish1[] = zoo.filter(isFish)
    const underWater2: Fish1[] = zoo.filter(isFish) as Fish1[]
    const underWater3: Fish1[] = zoo.filter((pet): pet is Fish1 => {
      if (pet.name === 'frog') {
        return false
      }
      return isFish(pet)
    })
    

受歧视的unions

  • // 求面积
    interface Circle {
      kind: 'circle'
      radius: number
    }
    interface Square {
      kind: 'square'
      sideLength: number
    }
    type Shape = Circle | Square
    
    function getArea(shape: Shape): number {
      switch (shape.kind) {
        case 'circle':
          return Math.PI * shape.radius ** 2
        case 'square':
          return shape.sideLength ** 2
      }
    }
    

never与穷尽性检查

  • never => 不应该存在的状态
    
    interface Circle {
      kind: 'circle'
      radius: number
    }
    interface Square {
      kind: 'square'
      sideLength: number
    }
    
    type Shape1 = Circle | Square
    
    function getArea(shape: Shape1): number {
      switch (shape.kind) {
        case 'circle':
          return Math.PI * shape.radius ** 2
        case 'square':
          return shape.sideLength ** 2
        default:
          // never可以进行穷尽性检查
          const _exhaustiveCheck: never = shape
          return _exhaustiveCheck
      }
    }
    

函数

函数类型表达式

  • fn: (a: string) => void
    
    type GreetFn = (a: string) => void
    
    function greet(fn: GreetFn): void {
      fn('hello world')
    }
    
    function printTo(s: string) {
      console.log(s)
    }
    
    greet(printTo)
    

调用签名

  • type DescribableFn = {
      description: string
      (someArg: number): boolean
    }
    
    function doSomething(fn: DescribableFn) {
      console.log(fn.description + ' returned:' + fn(6))
    }
    
    function fn1(n: number) {
      console.log(n)
      return true
    }
    fn1.description = 'hello'
    
    doSomething(fn1)
    

构造签名

  • class Ctor {
      s: string
      constructor(s: string) {
        this.s = s
      }
    }
    
    type someConstructor = {
      new (s: string): Ctor
    }
    
    function fn(ctor: someConstructor) {
      return new ctor('hello')
    }
    
    const f = fn(Ctor)
    console.log(f.s)
    
    interface CallOrCtor {
      new (s: string): Date
      (n?: number): number
    }
    
    function fn1(date: CallOrCtor) {
      let d = new date('2021-12-21')
      let n = date(100)
    }
    

泛型函数

  • 输入的类型与输出的类型有关,或者两个输入的类型以某种方式相关联
    

类型推断

  • function firstElement(arr: any[]) {
      return arr[0]
    }
    firstElement(['a', 'b', 'c'])
    
    // 泛型的作用是确保输入的元素类型和输出的元素类型一致
    function firstElement1<T>(arr: T[]): T | undefined {
      return arr[0]
    }
    // 可以不用加<string>,ts会自动推断类型,加上反而会限制参数类型
    firstElement1<string>(['a', 'b', 'c'])
    firstElement1(['a', 'b', 'c'])
    firstElement1<number>([1, 2, 3])
    firstElement1<undefined>([])
    
    function map<I, O>(arr: I[], func: (arg: I) => O): O[] {
      return arr.map(func)
    }
    // 根据类型推断I类型为string,O类型为number
    const parsed = map(['1', '2', '3'], (n) => parseInt(n))
    

限制条件

  • // 限制条件并非扩展条件 要求传入的a和b必须包含length属性
    function longest<T extends { length: number }>(a: T, b: T) {
      if (a.length >= b.length) {
        return a
      } else {
        return b
      }
    }
    
    const longerArr = longest([1, 2], [2, 3, 4])
    const longerStr = longest('a', 'b')
    

使用受限值

  • function minimum<T extends { length: number }>(obj: T, minimum: number): T {
      if (obj.length >= minimum) {
        return obj
      } else {
        // 不能返回确定的对象,需要根据入参来确定返回的类型1,应该返回的是一个泛型
        return { length: minimum } // 报错
      }
    }
    

指定类型参数

  • function combine<T>(arr1: Array<T>, arr2: T[]): T[] {
      return arr1.concat(arr2)
    }
    // 指定参数类型(不推荐,一般<string | number>用是用来指定入参类型)
    const arr = combine<string | number>([1, 2, 3], ['a', 'b', 'c'])
    console.log(arr)
    

编写优秀通用函数准则

  • 可能的情况下,使用类型参数本身,而不是对其进行约束

  • 总是尽可能少地使用类型参数

  • 如果一个类型的参数只出现在一个地方,请考虑是否真的需要

  • // 1.尽可能使用类型参数本身T,而不是使用extends对其进行约束(推荐) ts推断返回的类型是T
    function firstEl<T>(arr: T[]) {
      return arr[0]
    }
    // 不推荐 ts推断返回的类型是any
    function firstEl2<T extends any[]>(arr: T) {
      return arr[0]
    }
    const a = firstEl([1, 2, 3])
    const b = firstEl2([1, 2, 3])
    
    // 2.使用更少的类型参数,不要单独定义一个类型参数 (推荐)
    function filter<T>(arr: T[], func: (arg: T) => boolean) {
      return arr.filter(func)
    }
    // 不推荐
    function filter2<T, F extends (arg: T) => boolean>(arr: T[], func: F) {
      return arr.filter(func)
    }
    
    // 3.如果一个类型的参数只出现在一个地方,没有必要再次定义 (推荐)
    function greet(s: string) {
      console.log('hello', +s)
    }
    // 不推荐
    function greet2<Str extends string>(s: Str) {
      console.log('hello', +s)
    }
    

可选参数

  • // ? 表示可以选择是否传入参数
    function f(x?: number) {
    	// ...
    }
    // 也可以设置默认值  
    function fn(n: number = 1) {
      console.log(n.toFixed())
      console.log(n.toFixed(3))
    }
    f()
    f(10)
    

回调中的可选参数

  • 当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数

    function myForEach(
      arr: any[],
      callback: (arg: any, index?: number) => void
    ): void {
      for (let i = 0; i < arr.length; i++) {
        callback(arr[i], i)
        callback(arr[i])
      }
    }
    
    myForEach([1, 2, 3], (a) => {
      console.log(a)
    })
    
    myForEach([1, 2, 4], (a, i) => {
      console.log(a, i)
    })
    
    // 当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数
    myForEach([1, 2, 4], (a, i) => {
      console.log(i?.toFixed())
    })
    

函数重载

基本语法

  • // 重载签名:
    function makeDate(timestamp: number): Date
    function makeDate(m: number, d: number, y: number): Date
    // 实现签名:
    function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
      // ...
    }
    // 重载签名:
    function makeDate(timestamp: number): Date
    function makeDate(m: number, d: number, y: number): Date
    // 实现签名:
    function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
      if (d && y) {
        return new Date(y, mOrTimestamp, d)
      } else {
        return new Date(mOrTimestamp)
      }
    }
    const d1 = makeDate(12314213)
    const d2 = makeDate(5, 6, 7)
    
    

重载签名和实现签名

  • 3个问题:
    	参数不正确
    	参数类型不正确
    	返回类型不正确
    
    function f3n(x: string): void
    function f3n() {}
    // 1.参数不正确,必须要传参数
    f3n('hello')
    
    function f6n(x: boolean): void
    function f6n(x: string): void
    // 2.参数类型不正确,必须兼容所有重载函数的入参类型
    function f6n(x: boolean | string) {}
    
    function f7n(x: string): string
    function f7n(x: boolean): boolean
    // 3.返回类型不正确,必须兼容所有重载函数的返回值
    function f7n(x: string | boolean): string | boolean {
      // return 'hello'
      return true
    }
    

编写好的重载

  • 在可能的情况下,总是倾向于联合类型的参数而不是重载参数

  • // 不推荐
    // function len(s: string): number
    // function len(arr: any[]): number
    // function len(x: any) {
    //   return x.length
    // }
    
    // 推荐 应该使用联合类型的参数而不是重载参数
    function len(x: any[] | string) {
      return x.length
    }
    len('hello')
    len([1, 2, 3])
    len(Math.random() > 0.5 ? 'hello' : [4, 5, 6])
    

函数内this的声明

  • interface User {
      admin: boolean
    }
    interface DB {
      filterUsers(filter: (this: User) => boolean): User[]
    }
    
    const db: DB = {
      filterUsers: (filter: (this: User) => boolean) => {
        let user1: User = {
          admin: true
        }
        let user2: User = {
          admin: false
        }
        return [user1, user2]
      }
    }
    // 不能使用箭头函数,不包含this参数
    const admins = db.filterUsers(function (this: User) {
      return this.admin
    })
    console.log(admins)
    

其他类型(void,object,unknown,never,Function)

void

  • 表示没有返回值函数的返回值;

  • void和undefined不一样

  • function noop() {
    	return; // ts可以推断出返回类型是void 
    }
    

object

  • 指的是任何的**不是基元(string,number,bigint,symbol,boolean,null,undefined)**的值
  • object不同于**{ },也不同于Object**
  • object不是Object始终使用object!

unknow

  • 代表任何值,与any类似,但是更安全,因为对未知unknow值做的任何事情都是不合法的

  • function f1(a: any) {
    	a.b(); // 正确
    }
    function f2(a: unknown) {
    	a.b(); // 报错
    }
    

never

  • 表示永远不会被观察到的值

  • 返回值是异常/终止程序执行/死循环的话,返回类型会被推断为never

  • function fail(msg: string): never {
    	throw new Error(msg); // 返回值是异常
    }
    
    function fn(x: string | number) {
        if(typeof x === 'string') {
    		// ...
        } else if(typeof x === 'number') {
    		// ...
        } else {
            x; // 'never'类型
        }
    }
    

Function

  • 全局性的Function类型描述了诸如 bind、call、apply 和其他存在于JS中所有函数值的属性

  • 还有一个特殊的属性,即Function类型的值总是可以被调用,这些调用返回any

  • 推荐使用() => void方法来定义类型

  • function doSomething(f: Function) {
    	return f(1,2,3) // 返回值是any,不安全,所以不推荐这样做
    }
    // 如果需要接收一个任意的函数,但是不打算调用它,推荐使用以下方法来定义类型
    () => void
    

参数展开运算符

形参展开

  • function multiply(n: number, ...m: number[]): number[] {
      return m.map((x) => n * x)
    }
    const newA = multiply(10, 1, 2, 3, 4)
    console.log(newA) // [ 10, 20, 30, 40 ]
    

实参展开

  • const arr1 = [1, 2, 3]
    const arr2 = [4, 5, 6]
    
    arr1.push(...arr2)
    console.log(arr1)
    
    // 因为Math.atan2要求确定的2个参数,应该是不可变的参数,所以要对args使用as断言
    const args = [8, 5] as const
    const angle = Math.atan2(...args)
    console.log(angle) // 1.0121970114513341
    

参数解构

  • type ABC = { a: number; b: number; c: number }
    function sum({ a, b, c }: ABC) {
      console.log(a + b + c)
    }
    
    sum({ a: 1, b: 2, c: 3 })
    

返回void类型

  • 一个具有void返回类型的上下文函数(type vf = () => void),在实现时,可以返回任何其他的值,但它会被忽略

  • 当一个字面的函数定义有一个void的返回类型时,该函数必须不返回任何东西

    // 一个具有void返回类型的上下文函数(type vf = () => void),在实现时,可以返回任何其他的值,但它会被忽略
    type vf = () => void
    const f1: vf = () => {
      return 100 // 可以返回任何类型
    }
    
    // 当一个字面的函数定义有一个void的返回类型时,该函数必须不返回任何东西
    function f4(): void {
      // return 'a' // 不能返回非void类型
    }
    const f5 = function (): void {
      // return true  // 不能返回非void类型
    }
    

对象

对象类型

  • 匿名对象

  • 接口命名

  • 类型别名

  • // 1.匿名对象,使用对象字面量定义对象类型
    function greet(person: {name: string, age: number}) {
      return 'Hello ' + person.name
    }
    
    // 2.接口命名,使用接口定义对象类型
    interface Person2{
      name: string
      age: number
    }
    function greet2(person: Person2) {
      return 'Hello ' + person.name
    }
    
    // 3.类型别名,使用类型别名对象类型
    type Person3 = {
      name: string
      age: number
    }
    function greet3(person: Person3) {
      return 'Hello ' + person.name
    }
    

属性修改器

可选属性

  • type Shape = {}
    
    interface PaintOpts {
      shape: Shape
      xPos?: number
      yPos?: number
    }
    
    // 解构后的属性后的alias是shape的别名,并不是类型限制
    function paintShape({ shape: alias, xPos = 0, yPos = 0 }: PaintOpts) {
      console.log(alias, xPos, yPos)
    }
    const shape: Shape = {}
    paintShape({ shape })
    paintShape({ shape, xPos: 100 })
    paintShape({ shape, xPos: 100, yPos: 100 })
    

只读属性

  • interface SomeType {
      readonly prop: string // 只读属性
      readonly friends: {
        name: string
      }
    }
    function doSomething(obj: SomeType) {
      console.log(obj.prop)
      // obj.prop = 'hello' // 只读属性(基元类型)无法被修改
      obj.friends.name = 'newName' // 只读属性(对象类型)下的属性可以被修改,对象属性本身不能被修改
    }
    
    // readonly属性可以通过类型别名改变
    interface Person {
      name: string
      age: number
    }
    interface ReadonlyPerson {
      readonly name: string
      readonly age: number
    }
    let writablePerson: Person = {
      name: 'aa',
      age: 18
    }
    // 可以把只读的属性通过可写的属性进行赋值
    let readonlyPerson: ReadonlyPerson = writablePerson
    
    console.log(readonlyPerson.age) // 18
    writablePerson.age++
    console.log(readonlyPerson.age) // 19
    

索引签名

  • 不能提前知道一个类型的所有属性名称,但是知道这个值的形状,这种情况下可以使用一个索引签名来描述可能的值的类型

    interface StrArr {
      // 索引签名可以任意的给一个对象定义属性
      [index: number]: string
    }
    const myArr: StrArr = ['A', 'B']
    const secondItem = myArr[0]
    
    interface TestStr {
      [prop: string]: number
    }
    const testStr: TestStr = {
      x: 100,
      y: 200
    }
    
    interface Animal {
      name: string
    }
    interface Dog extends Animal {
      breed: string
    }
    interface NotOk {
      [index: string]: number | string
      length: number
      name: string
    }
    let notOk: NotOk = {
      x: 100,
      length: 12,
      name: 'aa'
    }
    
    interface ReadonlyStrArr {
      readonly [index: number]: string
    }
    let myA: ReadonlyStrArr = ['a', 'b']
    // myA[0] = 'c' // 只读属性不能修改
    

扩展类型

  • 接口允许我们通过扩展其他类型建立新的类型

    interface BasicAddress {
      name?: string
      street: string
      city: string
      country: string
    }
    interface Colorful {
      color: string
    }
    
    // 可以继承多个接口 使用 , 表示继承多个接口
    interface AddressWithUnit extends BasicAddress, Colorful {
      unit: string
    }
    
    let awu: AddressWithUnit = {
      unit: '3单元',
      street: '清河街道',
      city: '北京',
      country: '中国',
      name: '',
      color: 'blue'
    }
    

交叉类型

  • 用于组合现有的对象类型

    interface Colorful {
      color: string
    }
    interface Circle {
      radius: number
    }
    
    // 可以组合多个接口 使用 & 表示组合多个接口 
    // 1.通过type定义交叉类型
    type ColorfulCircle = Colorful & Circle
    let cc: ColorfulCircle = {
      color: 'red',
      radius: 100
    }
    
    // 2.通过匿名对象的方法定义交叉类型
    function draw(circle: Colorful & Circle) {
      console.log(circle.color)
      console.log(circle.radius)
    }
    draw({
      color: 'red',
      radius: 100
    })
    

处理冲突(接口与交叉类型)

  • interface Sister {
      name: string;
    }
    interface Sister {
      age: number;
    }
    // 接口可以定义同名的,同名接口会合并,可以实现类型合并
    let sister: Sister = {
      name: 'aaa',
      age: 22
    }
    
    // 类型别名无法定义同名类型,可以避免类型冲突
    type Brother = {
      name: string
    }
    // type Brother = {
    //   age: number
    // }
    

泛型对象类型

  • // 定义对象类型
    
    interface Box {
      // 1. any,用户可以传入任何类型,失去了定义类型的意义(不推荐)
      contents: any
      // 2.unknown, 不能做任何操作 (不推荐),1.使用类型缩小(加判断)2.类型断言
      contents2: unknown
    }
    let box: Box = {
      contents: 'hello',
      contents2: 'hello'
    }
    
    // 3.函数重载 代码冗余,不宜阅读(不推荐)
    interface NumberBox {
      contents: number
    }
    interface StrBox {
      contents: string
    }
    interface BoolBox {
      contents: boolean
    }
    function setContents(box: StrBox, newContents: string): void
    function setContents(box: NumberBox, newContents: number): void
    function setContents(box: BoolBox, newContents: boolean): void
    function setContents(
      box: { contents: string | boolean | number },
      newContents: boolean | number | string
    ): void {
      box.contents = newContents
    }
    
    // 4.泛型对象类型(推荐)灵活,代码量小
    interface BoxFin<T> {
      contents: T
    }
    let boxFin: BoxFin<string> = {
      contents: 'hello'
    }
    
    // 使用type定义新的类型
    // 使用interface定义泛型对象类型
    interface Box2<T> {
      contents: T
    }
    interface Apple {
      // ...
    }
    let a: Apple = {}
    type AppleBox = Box2<Apple>
    let ab: AppleBox = {
      contents: a
    }
    
    // 使用类型别名定义泛型对象类型
    // 不仅可以描述对象的类型,还可以编写其他类型的通用的辅助类型
    type Box3<T> = {
      contents: T
    }
    type OrNull<T> = T | null
    type OneOrMany<T> = T | T[]
    type OneOrManyOrNull<T> = OrNull<OneOrMany<T>> 
    type OneOrManyOrNullOrString<T> = OneOrManyOrNull<string>
    

类型操纵

从类型中创建类型

  • 用现有的类型或者值来表达一个新的类型的方法
    • 泛型类型
    • keyof类型操作符
    • typeof类型操作符
    • 索引访问类型
    • 条件类型
    • 映射类型
    • 模板字面量类型

泛型

使用通用类型变量

  • function loggingIdentity<T>(arg: Array<T>): T[] {
      console.log(arg.length)
      return arg
    }
    loggingIdentity([1, 2, 3])
    

泛型类型

  • function identity<T>(arg: T): T {
      return arg
    }
    let myIdentity: <T>(arg: T) => T = identity // 方法
    let myIdentity2: { <T>(arg: T): T } = identity // 对象字面量方式
    
    interface GenericIdentityFn {
      <T>(arg: T): T
    }
    let myIdentity3: GenericIdentityFn = identity // 泛型接口方式
    
    interface GenericIdentityFn2<T> {
      (arg: T): T
    }
    let myIdentity4: GenericIdentityFn2<string> = identity // 泛型接口方式 更加严谨
    

泛型类

  • 泛型类的形状和泛型接口类似,在类的名称后面加个<>
    
    // 初始化未赋值ts会报错
    // 在tsconfig.json中修改为
    // "strictPropertyInitialization": false,
    
    class GenericNumber<T> {
      zeroVal: T
      add: (x:T, y:T) => T
    }
    
    let mygeneric = new GenericNumber<number>()
    mygeneric.zeroVal = 0 
    mygeneric.add = function(x, y) {
      return x+y
    } 
    

泛型约束

  • interface LengthWise {
      length: number
    }
    
    function loggingIdenity<T extends LengthWise>(arg: T): T {
      arg.length
      return arg
    }
    
    // 可以传入具有lengths属性的值
    loggingIdenity('hello')
    loggingIdenity([1,2,3,])
    

在泛型中使用类型参数

  • // k类型一定属于T类型的对象类型的某个key
    function getProperty<T, K extends keyof T>(obj: T, key: K) {
      return obj[key]
    }
    
    let x = {
      a: 1,
      b: 2,
      c: 3,
      d: 4
    }
    // 必须传入x中存在的key
    getProperty(x, 'a')
    // getProperty(x, 'e') // 报错
    

在泛型中使用类类型

  • function create<T>(c: { new (): T }): T {
      return new c()
    }
    
    class Beekeeper {
      hasMask: boolean = true
    }
    class Zookeeper {
      nametag: string = 'Mikle'
    }
    class Animal {
      numLegs: number = 4
    }
    class Bee extends Animal {
      keeper: Beekeeper = new Beekeeper()
    }
    class Lion extends Animal {
      keeper: Zookeeper = new Zookeeper()
    }
    
    function createInstance<A extends Animal>(c: new () => A): A{
      return new c()
    }
    
    // extends Animal进行约束,所以传入的参数(类)中必须含有numLegs属性
    createInstance(Lion).keeper.nametag
    createInstance(Bee).keeper.hasMask
    // createInstance(BeeKeeper) // 报错
    

keyof类型操作符

  • 接受一个对象类型,会产生它的key的字符串或者是数字字面量的一个结合,或者是一个联合类型

  • 常用于映射类型组合

    type Point = {
      x: number
      y: number
    }
    type P = keyof Point
    // 只能赋值Point类型中存在的key
    const p1: P = 'x'
    const p2: P = 'y'
    // const p3: P = 'z' // 报错
    
    // 索引签名
    type Arr = {
      [n: number]: unknown
    }
    type A = keyof Arr
    // 只能赋值索引签名(number)类型的值
    const a: A = 0
    // const b: A = 'b' // 报错
    
    type Mashish = {
      [k: string]: boolean
    }
    type M = keyof Mashish
    // 只能赋值索引签名(number | string)类型的值
    const m1: M = 's'
    const m2: M = 100
    // const m3: M = true // 报错 
    

typeof类型操作符

  • 可以在类型的上下文中使用typeof引用一个变量,或者是属性的类型

    console.log(typeof 'hello') // string
    
    let s = 'hello'
    let n: typeof s
    // n的类型为s的类型
    n = 'world'
    // n = 100 // 报错
    
    // ReturnType<T>  获取函数返回值类型
    type Predicate = (x: unknown) => boolean
    type K = ReturnType<Predicate>
    
    function f() {
      const arr = ['1', '2']
      return arr.map((x) => Number(x))
    }
    // 只能使用typeof定义类型
    type P2 = ReturnType<typeof f>
    const p3: P2 = [1, 2, 3]
    
    function f2() {
      return {
        x: 10,
        y: 10
      }
    }
    type P3 = ReturnType<typeof f2>
    const p4: P3 = {
      x: 1,
      y: 1
    }
    
    // 可以用typeof标识标识符,变量以及变量的属性,但不能用来标识类似于函数的结果...
    function msgBox() {}
    let shouldContinue: typeof msgBox 
    shouldContinue = () => {}
    

索引访问类型

  • 用来查询另一个类型上特定的属性

    type Person = {
      age: number
      name: string
      alive: boolean
    }
    type Age = Person['age'] // Person中age的类型
    let age: Age = 1
    
    interface Person2 {
      name: string
      age: number
      alive: boolean
    }
    type I1 = Person2['age' | 'name'] // Person2中age和name的类型
    const i1: I1 = 1
    const i2: I1 = 'w'
    
    type I2 = Person2[keyof Person2] // 返回Person2中所有key组成的联合类型
    const i3: I2 = 1
    const i4: I2 = 'w'
    const i5: I2 = true
    
    type AliveOrName = 'alive' | 'name'
    type I3 = Person2[AliveOrName] // Person2中alive和name的类型
    const i6: I3 = 'w'
    const i7: I3 = true
    
    // type I4 = Person2['unAlive'] // 报错,不能定义Person2中未存在的类型
    
    const MyArr = [
      {
        name: 'Mike',
        age: 15
      },
      {
        name: 'Bob',
        age: 23
      },
      {
        name: 'Marry',
        age: 35
      }
    ]
    type Person3 = typeof MyArr[number] // 获取MyArr某个元素的类型(age和name的类型)
    const pp: Person3 = {
      name: 'Jack',
      age: 13
    }
    
    type Age2 = typeof MyArr[number]['age'] // 获取MyArr某个元素的类型(age的类型)
    const age2: Age2 = 12
    
    type Age3 = Person3['age'] // 获取Person3中age的类型
    const age3: Age3 = 33
    
    // 不能使用const定义变量引用,必须使用type
    const key = 'age'
    // type Age4 = Person3[key]  // 报错 const定义的变量不能作为索引类型使用。
    type Age5 = Person3[typeof key]  // 1.可以使用typeof获取变量类型作为变量引用
    type key = 'age'
    type Age6 = Person3[key]  // 2.可以通过type定义key的类型作为变量引用
    

条件类型

  • // 用于描述输入和输出之间类型的关系
    SomeType extends OtherType ? TrueType : FalseType
    
    interface Animal {
      live(): void
    }
    interface Dog extends Animal {
      woof(): void
    }
    
    type Example1 = Dog extends Animal ? number : string // Example1的类型为number
    
    type Example2 = RegExp extends Animal ? number : string // Example的类型为string
    
    interface IdLabel {
      id: number
    }
    interface NameLabel {
      name: string
    }
    // 函数重载方式 代码量大
    function createLabel(id: number): IdLabel
    function createLabel(name: string): NameLabel
    function createLabel(nameOrId: string | number): NameLabel | IdLabel
    function createLabel(nameOrId: string | number): NameLabel | IdLabel {
      throw ''
    }
    // 使用条件类型 简单
    type NameOrId<T extends string | number> = T extends number
      ? IdLabel
      : NameLabel
    function createLabel2<T extends string | number>(nameOrId: T): NameOrId<T> {
      throw ''
    }
    
    let a1 = createLabel('tabs') // a1的类型为NameLabel
    let b1 = createLabel(2) // b1的类型为IdLabel
    let c1 = createLabel(Math.random() > 0.5 ? 'hello' : 2) // c1的类型为 NameLabel | IdLabel
    

条件类型约束

  • type MessageOf<T> = T extends {message: unknown} ? T['message'] : never;
    
    type MsgOf<T extends { message: unknown }> = T['message']
    
    type MessageOf<T> = T extends { message: unknown } ? T['message'] : never
    interface Email {
      message: string
    }
    type EmailMessage = MessageOf<Email> // 即string类型
    let email: EmailMessage = 'hello'
    
    interface Dog {
      bark(): void
    }
    type DogMessage = MessageOf<Dog> // 即never类型
    let dog: DogMessage = '1' as never
    
    type Flatten<T> = T extends any[] ? T[number] : T
    type Str = Flatten<string[]> // 即string类型
    let str: Str = 'a'
    type Num = Flatten<number> // 即number类型
    let num: Num = 1
    

在条件类型内进行推理

  • // 推断我们在真实分支中使用infer关键字来进行对比的类型
    type Flatten<T> = T extends Array<infer Item> ? Item : T;
    
    type GetReturnType<T> = T extends (...args: never[]) => infer Return
      ? Return
      : never
    
    type Num1 = GetReturnType<() => number> // Num1的类型即number
    let num1: Num1 = 100
    
    type Str2 = GetReturnType<(x: string) => string> // Num1的类型即string
    let str2: Str2 = 'a'
    
    type Bool2 = GetReturnType<(a: boolean) => boolean[]> // Bool2的类型即boolean[]
    let bool2: Bool2 = [true, false]
    
    type Never = GetReturnType<string> // 没有返回值,type不被infer Return约束, Never的类型即never
    let never: Never = 'a' as never
    
    // 当有一个具有多个调用签名类型(比如重载函数),传到infer Return中进行推断的时候 一般从最后一个签名进行推断,不会根据参数类型的列表来重新执行推断的解析
    function strOrNum(x: string): number
    function strOrNum(x: number): string
    function strOrNum(x: number | string): string | number
    function strOrNum(x: number | string): string | number {
      return Math.random() > 0.5 ? 'hello' : 1
    }
    type T1 = ReturnType<typeof strOrNum> // T1的类型即 string | number
    let t1: T1 = '2'
    let t2: T1 = 2
    

分布式条件类型

  • // 当条件类型作用于一个通用类型的时候,给定一个联合类型,其将变成为一个分布式条件类型
    type ToArr<T> = T extends any ? T[] : never
    type StrArrOrNumArr = ToArr<string | number> // 返回一个 string[] | number[] 类型的数组,相当于做了一个分布式的分发
    
    let sa1: StrArrOrNumArr = ['A', 'B']
    let sa2: StrArrOrNumArr = [1, 2]
    let sa3: StrArrOrNumArr = []
    
    type ToArrNotDist<T> = [T] extends [any] ? T[] : never 
    type StrArrOrNumArr2 = ToArrNotDist<string | number> // 返回一个 (string | number)[] 类型的数组,相当于禁止分布式的分发
    let sa4: StrArrOrNumArr2 = ['A', 'B', 1]
    let sa5: StrArrOrNumArr2 = []
    

映射类型

类成员

  • 类属性
  • readonly
  • 构造器
  • 方法
  • Getters/Setters
  • 索引签名

类属性

  • class Point {
      x
      y
      constructor() {
        this.x = 0
        this.y = 0
      }
    }
    const pt = new Point()
    pt.x = 1
    pt.y = 1
    
    class OkGreeter {
      name!: string
    }
    

readonly

  • class Greeter {
      readonly name: string = 'init'
      constructor(name?: string) {
        if (name) {
          this.name = name
        }
      }
      err() {
        // this.name = 'not' // 报错,只读属性无法修改
      }
    }
    
    const g = new Greeter()
    // g.name = 'hello'; // 报错,只读属性无法修改
    console.log(g.name);
    

构造器

  • 构造函数不能有类型参数

  • 构造函数不能有返回类型注释

    class Point1 {
      x: number
      y: number
    
      constructor(x:number = 0, y: number = 0) {
        this.x = x
        this.y = y
      }
    }
    
    const p = new Point1()
    console.log(p);
    
    class Base {
      k = 4
    }
    class Derived extends Base {
      constructor() {
        super()
        console.log(this);
        
      }
    }
    const d = new Derived()
    

方法

  • class Point2 {
      x: number = 10
      y: number = 10
    
      scale(n: number): void {
        this.x *= n
        this.y *= n
      }
    }
    const t = new Point2()
    t.scale(10)
    console.log(t)
    
    let x: number = 12
    class C {
      x: string = 'hello'
    
      m() {
        this.x = 'world' // this.x是类中实例化后的x
        x = 120 // 这个x是外部的x
      }
    }
    

Getters/Setters(访问器)

  • 如果存在get,但没有set,则该属性自动是只读

  • 如果没有指定setter参数的类型,它将从getter的返回类型中推断出来

  • 访问器和设置器必须有相同的成员可见性

  • TS4.3开始,set新值的类型可以和get返回值的类型不同

    class C1 {
      _length = 0
      get length() {
        return this._length
      }
      set length(value) {
        this._length = value
      }
    }
    let c1: C1 = new C1()
    console.log(c1.length) // 0
    c1.length = 100
    console.log(c1.length) // 100
    
    class Thing {
      _size = 0
      get size(): number {
        return this._size
      }
      set size(value: number | string | boolean) {
        let num = Number(value)
        if (!Number.isFinite(num)) {
          this._size = 0
          return
        }
        this._size = num
      }
    }
    let t1: Thing = new Thing()
    console.log(t1.size);
    t1.size = 900
    t1.size = 'hello'
    

索引签名

  • class MyClass {
      [s: string]: boolean | ((s: string) => boolean)
      x = true
      check(s: string) {
        return this[s] as boolean
      }
    }
    

类继承

implements子句

  • implements实现一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能

  • 可以实现一个类去继承或者实现一个接口

    interface Pingable {
      ping(): void
    }
    
    class Sonar implements Pingable {
      ping() {
        console.log('ping')
      }
    }
    
    class Ball implements Pingable {
      ping() {}
      pong() {}
    }
    
    interface A {}
    interface B {}
    class AB implements A, B {}
    
    // implements不会改变类的类型
    interface Checkable {
      check(name: string): boolean
    }
    
    class NameCheck implements Checkable {
      check(s: string) {
        return s.toLowerCase() === 'ok'
      }
    }
    
    
    interface A1 {
      x: number
      y?: number
    }
    class C2 implements A1 {
      x = 0
    }
    const c2 = new C2()
    console.log(c2) // 不存在c2.y属性
    

extends子句

  • 可以实现一个类继承另一个类,被继承的类称之为基类或父类,继承的类称之为子类, 继承完之后,具备父类所有属性和方法,还可以定义自己的一些成员

    class Animal {
      move() {
        console.log('moving');
      }
    }
    
    class Dog extends Animal {
      woof(times: number) {
        for (let i = 0; i < times; i++) {
          console.log('woof!');
        }
      }
    }
    
    const dog = new Dog();
    dog.move()
    dog.woof(3)
    

重写方法

  • 派生类可以覆盖基类的一个字段或者属性

  • 使用super.xxx访问基类的方法

  • TS要求派生类总是他的基类的一个子类型

    class Ba {
      greet() {
        console.log('greet!')
      }
    }
    
    class De extends Ba {
      greet(name?: string) {
        if (name) {
          console.log('你好,', name)
        } else {
          super.greet()
        }
      }
    }
    const de = new De()
    de.greet() // greet!
    de.greet('zzy') // 你好, zzy
    
    const b: Ba = de
    b.greet() // greet!
    

初始化顺序

  • 基类的字段被初始化

  • 基类构造函数运行

  • 派生类的字段被初始化

  • 派生类构造函数运行

    class Base1 {
      name = 'base1'
      constructor() {
        console.log(this.name); // base1
      }
    }
    class Derived1 extends Base1 {
      name = 'derived1'
      constructor() {
        super()
        console.log(this.name); // derived1 
      }
    }
    const derived1 = new Derived1(); 
    

继承内置类型

  • 继承TS内置对象,如Error,Array,Map...

    class MsgError extends Error {
      constructor(m: string) {
        super(m)
    
        // 对于低版本JS环境,可以明确设置原型
        Object.setPrototypeOf(this, MsgError.prototype)
      }
    
      sayHello() {
        return '发现' + this.message
      }
    }
    const me = new MsgError('错误')
    console.log(me.sayHello()) // 发现错误
    
    const mse = new MsgError('错误!')
    console.log(mse instanceof MsgError) // true
    

成员的可见性

public

  • 公开的,默认值。任何对象在任何地方都可以访问

    class Gerrt {
      public greet() {
        console.log('hi');
      }
      sayHello() {
        this.greet() //当前类中访问
      }
    }
    class Hello extends Gerrt {
      constructor() {
        super();
        this.greet() // 子类中访问
      }
    }
    const g1 = new Gerrt()
    g1.greet() // 实例中访问
    g1.sayHello()
    

protected

  • 受保护的。能在当前类或者子类中进行访问

  • 派生类可以暴露基类受保护的成员

    class Greet2 {
      public greet() {
        console.log(this.getName()) // 当前类中可以访问
      }
      protected getName() {
        return 'hello'
      }
    }
    
    class SpecialGreet extends Greet2 {
      public howdy() {
        console.log(this.getName()) // 子类中可以访问
      }
    }
    
    const g3 = new SpecialGreet()
    g3.greet()
    g3.howdy()
    // g3.getName() // 报错,实例中不能访问
    
    class Ba1 {
      protected m = 10
    }
    class Derived2 extends Ba1 {
      public m = 15 // 子类中可以修改父类中某个属性的访问权限
    }
    const d3 = new Derived2()
    console.log(d3.m) // 15 子类修改父类属性的访问权限之后可以在实例中访问
    

private

  • 私有的。只能在当前类中进行访问

  • TS允许跨实例的私有访问

    class Base3 {
      private x = 0
    
      printX() {
        console.log(this.x); // 1.当前类中可以访问
      }
    }
    
    class Derived3 extends Base3 {
      showX() {
        // console.log(this.x); // 报错,2.子类中不能访问
      }
    }
    
    const de3 = new Derived3();
    // console.log(d.x); // 报错,3.实例中无法访问
    
    // 4.TS允许跨实例的私有访问
    class AAA {
      private x = 10
    
      public sameAs(other: AAA) {
        return other.x === this.x // 载一个类里可以访问另外一个实例的属性
      }
    }
    

静态成员

  • 不与类的特定实例相关联,通过类的构造函数对象本身访问

  • 特殊的静态名称不安全,避免使用:name,length,call等

  • **TS没有静态类的概念,因为我们还有函数和普通对象 **

    class MyClass1 {
      private static x = 0
      static printX() {
        console.log(MyClass1.x)
      }
    }
    // console.log(MyClass1.x); // 报错 私有静态属性只能在类中访问
    MyClass1.printX() // 0
    
    // 静态成员可以被继承
    class Base4 {
      // static name = 's' // 报错,特殊的静态名称不安全
      static getHello() {
        return 'hello'
      }
    }
    class Derived4 extends Base4 {
      myHello = Derived4.getHello()
    }
    
    // TS没有静态类的概念,因为我们还有函数和普通对象
    class StaticClass {
      static doSomething() {}
    }
    function doSomething() {}
    const helper = {
      doSomething() {}
    }
    

类里的static区块

  • class Foo {
      static #count = 0
      get count() {
        return Foo.#count
      }
      // static {} 可以在区块内部访问class内部的#开头的私有变量
      static {
        try {
          const last = {
            length: 100
          }
          Foo.#count += last.length
        } catch (error) {}
      }
    }
    // Foo.#count // 报错,#定义的私有变量无法在外部被访问
    

泛型类

  • class Box<T> {
      contents: T
      constructor(value: T) {
        this.contents = value
      }
      // static defaultVal: T // 报错,静态成员不能使用泛型
    }
    
    const bb: Box<string> = new Box('hello') // bb为string类型
    bb.contents = 'str'
    const bb2 = new Box<boolean>(true) // bb2为boolean类型
    bb2.contents = false
    const bb3 = new Box(12) // 进行类型推断,bb3为number类型
    bb3.contents = 100
    

类运行时中的this

  • class MyClass2 {
      name = 'MyClass2'
      getName() {
        return this.name
      }
      // 箭头函数中的this永远指向当前词法作用域
      getName2 = () => {
        return this.name
      }
      // 使用this参数
      getName3(this: MyClass2) {
        return this.name
      }
    }
    const c = new MyClass2()
    const obj = {
      name: 'obj',
      getName: c.getName,
      getName2: c.getName2,
      getName3: c.getName3
    }
    
    const gg = c.getName3
    // console.log(gg()); // 报错,类型为“void”的 "this" 上下文不能分配给类型为“MyClass2”的方法的 "this"。
    
    console.log(c.getName()); // MyClass2
    console.log(c.getName2()); // MyClass2
    console.log(c.getName3()); // MyClass2
    console.log(obj.getName()); // obj
    console.log(obj.getName2()); // MyClass2
    console.log(obj.getName3()); // obj
    

this类型

  • 会动态的指向当前类的类型

    class Box2 {
      content: string = ''
      set(value: string) {
        this.content = value
        return this
      }
    }
    class ClearBox extends Box2 {
      clear() {
        this.content = ''
      }
    }
    const a = new ClearBox()
    const bd = a.set('hello')
    console.log(bd)
    
    class NewBox {
      content: string = ''
      sameAs(other: this) {
        return other.content === this.content
      }
    }
    class Der extends NewBox {
      otherContent: string = '?'
    }
    
    const bas = new NewBox()
    const der = new Der()
    // der.sameAs(bas) // 报错,类型“NewBox”的参数不能赋给类型“Der”的参数。类型 "NewBox" 中缺少属性 "otherContent",但类型 "Der" 中需要该属性。
    

基于类型守卫的this

  • 可以在类和接口的方法的返回的位置使用this is Typethis is是固定写法,Type是某种类型

  • 当与类型缩小混合使用时,目标对象的类型将被缩小到指定的Type类型,这种现象被称为基于类型守卫的this

    class FileSystemObj {
      isFile(): this is FileRep {
        return this instanceof FileRep
      }
    
      isDirectory(): this is DirectoryRep {
        return this instanceof DirectoryRep
      }
    
      isNetwork(): this is NetworkRep & this {
        return this.network
      }
    
      constructor(public path: string, private network: boolean) {}
    }
    
    class FileRep extends FileSystemObj {
      constructor(path: string, public content: string) {
        super(path, false)
      }
    }
    class DirectoryRep extends FileSystemObj {
      children: FileSystemObj[]
      constructor(path: string) {
        super('', false)
        this.children = []
      }
    }
    interface NetworkRep {
      host: string
    }
    
    const fso: FileSystemObj = new FileRep('foo/bar', 'foo')
    if (fso.isFile()) {
      // const fso: FileRep
      fso.content
    } else if (fso.isDirectory()) {
      // const fso: DirectoryRep
      fso.children
    } else if (fso.isNetwork()) {
      // const fso: NetworkRep & FileSystemObj
      fso.host
    }
    
    class Boxs<T> {
      value?: T
      hasVal(): this is { value: T } {
        return this.value !== undefined
      }
    }
    const boxs = new Boxs()
    boxs.value = 'hello'
    if (boxs.hasVal()) {
      console.log(boxs.value) // hello
    }
    

参数属性

  • TS可以将构造函数的参数变成具有相同名称和值的一个属性,这些属性是类属性,称之为参数属性

  • 对应方法就是在构造函数的前面加上一些可见性的修饰符(public,private,protected,readonly...),由此产生的参数属性将会得到这些修饰符所修饰的含义

    class Params {
      constructor(public readonly x: number, protected y: number, private z: number) {
        this.x = x;
      }
    }
    
    const ps = new Params(100, 300, 400)
    console.log(ps.x);
    // ps.y // 报错,受保护属性只能在类和子类中访问
    // ps.z // 报错,私有属性只能在类中访问
    

类表达式

  • 类表达式与类的声明类似,但是类表达式不需要名字,可以理解为匿名类

    const someClass = class<T> {
      content: T
      constructor(content: T) {
        this.content = content
      }
    }
    const m = new someClass('hello')
    console.log(m.content) // hello
    

抽象类和成员

  • TS中的类 方法和字段可以是抽象的,一个抽象的方法或抽象的字段是一个没有提供实现的方法和字段,这些成员必须存在于一个抽象类中,不能直接实例化

  • 抽象类的作用是作为子类的基类,实现所有抽象成员

  • 当一个类没有任何抽象成员时,被称为具体类

    abstract class BaseClass {
      abstract getName(): string
    
      printName() {
        console.log(this.getName())
      }
    }
    
    // const bc = new BaseClass(); // 报错,抽象类无法被实例化,需要子类去继承抽象类
    
    class DerivedClass extends BaseClass {
      getName () {
        return 'world'
      }
    }
    const dc = new DerivedClass()
    dc.getName()
    dc.printName()
    
    // 抽象构造签名 不能new一个抽象类
    function greet(ctor: new () => BaseClass) {
      const instance = new ctor()
      instance.printName()
    }
    greet(DerivedClass)
    

类之间的关系

  • TS中大部分类结构相同的话可以进行比较

    class Po1 {
      x = 1
      y = 1
    }
    class Po2 {
      x = 0
      y = 0
    }
    // 同结构类型(属性类型都相等)的类可以相互替代,
    const po1: Po1 = new Po2()
    const po2: Po2 = new Po2()
    
    class Per {
      name: string = ''
      age: number = 100
    }
    class Employee {
      name: string = ''
      age: number = 22
      salary: number = 10
    }
    const pp: Per = new Employee() // TS内部进行判断,Employee被视为Per的子类
    
    class Empty {
    
    }
    function fn(x: Empty) {
      
    }
    // 可以把任何参数都当作空类的具体的实现
    fn(window)
    fn({})
    fn(fn)
    fn(100)
    

模块

认识模块

  • 语法:导出和导入的语法
  • 模块解析:模块名称(或路径)和磁盘上的文件之间的关系
  • 模块输出目标:编译出来的JS是什么样的

ES模块语法

  • maths.ts

    export var pi = 3.14
    export let squareTwo = 1.41
    export const phi = -1.61
    export class RandomNumberGenerator {}
    export function absolute(num: number): number {
      if (num < 0) {
        return num * -1
      }
      return num
    }
    
  • hello.ts

    export default function helloWorld() {
      console.log('helloWorld')
    }
    
  • app.ts

    import hello from './hello'
    
    import { pi, phi, absolute } from './maths'
    
    hello() // helloWorld
    console.log(pi); // 3.14
    console.log(absolute(phi)); // 1.61
    

额外的导入语法

  • import { old as new }

  • maths.ts

    export const pi = 3.14
    export default class RandomNumberGenerator {}
    
  • app.ts

    import RNGen, { pi as Π } from './maths'
    console.log(Π) // 3.14
    const rng = new RNGen()
    
  • app2.ts

    // 使用*导入所有内容
    import * as math from './maths'
    console.log(math.pi) // 3.14
    const rng = new math.default() // 使用default找到默认导出的内容
    

TS特定的ES模块语法

  • export type 导出类型
  • import type 导入类型
  • import { type Cat} 导入对象,对象中通过type指定类型

ES模块语法与CommonJS行为

  • import fs = require('fs')
    const code = fs.readFileSync('hello.ts', 'utf-8')
    

CommonJS语法

  • module

  • module.exports

  • require

  • maths.js

    function absoulte(num: number) {
      if (num < 0) {
        return num * -1
      }
      return num
    }
    // 1.导出方法
    module.exports = {
      pi: 3.14,
      squareTwo: 1.41,
      phi: -1.61,
      absoulte
    }
    // 2.导出方法 
    // exports.absoulte = absoulte
    
  • app.ts

    const maths = require('./maths')
    console.log(maths) 
    // maths:
    // {
    //   pi: 3.14,
    //   squareTwo: 1.41,
    //   phi: -1.61,
    //   absoulte: [Function: absoulte]
    // }