TS开发实战学习笔记

1,126 阅读8分钟

对象类型接口

  • 在ts中采用鸭式辨型法校验规则,对象接口只要包含就会校验通过,如果要添加多余属性,需要绕开校验的规则,绕开方式有三种
  • // 类型断言 A as B或者 <B>A
    // 使用变量
    // 使用字符串索引签名
    interface list {
      id: number
      }
      interface Result {
          data: list[]
      }
      
      function render (s:Result) {
      }
      let res = {
          data: [{
              id: 1,
              name: '2'
          }]
      }
      //使用变量
      render(res)
      //类型断言
      render(res as Result)
      //字符串索引签名,修改接口
      interface list {
          id: number
          [x: string]: any
      }
    

接口函数类型定义

  • 可选参数必须位于必选参数之后
   //接口定义
   interface fn {
       (x: number, y?: string): number
   }
   //变量定义
   let fn1: (x:number, y?: string) => number
   //类型别名定义
   type fn2 = (x: number, y?: string) => number
  • 函数重载
  function fn(...rest: number[]): number
  function fn(...rest: string[]): string
  <!--函数实现-->
  function fn (...rest: any[]): any {
      if (rest[0] && typeof rest[0] === 'string') {
          return rest.reduce((prev, next) => prev + next)
      } else if (rest[0] && typeof rest[0] === 'number') {
          return rest.reduce((prev, next) => prev * next)
      }
  }
  • 类属性描述符
    • public 默认所有属性都是public
    • private 只能被自己调用,不能被子类调用,如果构造函数是私有属性,则无法实例化和继承
    •  class Paren {
           private constructor () {
               
           }
       }
       <!---->
       new Paren() //错误
       class Child extends Paren {} //错误
      
    • protected 只能在类和子类的构造器中访问,不能再实例中访问,如果构造函数的constructor属性为protected,则该类不能被实例化,只能被继承,类似于基类
    •   class Parent {
            constructor () {
                this.protect()
            }
            protected protect () {}
        }
        const parent = new Parent
        parent.protect() //错误
      
    • readonly 只读属性,只读属性必须要分配值
    • static 只能通过自己调用,不能通过实例访问,原理为方法地址指向了Reflect.getPrototypeOf(Parent)
  • 类方法中参数的描述符
    • public 构造函数中的public描述符参数,默认为实例属性
    •   class Parent {
            constructor (public name: string, age: number) {
                this.name = 'Paernt'
            }
        }
      
  • 抽象类定义
  • <!--抽象类只能被继承,和上面的protected构造函数一样,不能用于实例化-->
     abstract class Parent {
         abstract getName (): void
     }
    class Child extends Parent {
        getName () {}
    }
    
  • 类的接口定义
    • 类实现接口时候,必须实现所有属性
    •  interface Human {
           name: string
           getName(): string
       }
       class Parent implements Human {
           constructor (public name: string) {
               this.name = name
           }
           getName () {
               return this.name
           }
       }
      
    • 接口只能约束类的公有成员,私有和构造函数等都不可以
    • <!--以下均为错误-->
        interface P {
          name: string,
          age: number,
          new (name: string): void
      }
      
      class PPS implements P{
          constructor (public name: string) {}
          private age: number
      }
      
  • 接口和类的继承之相爱相杀
    • 接口继承接口
    • interface T1 {
            name: string
        }
        interface T2 {
            age: number
        }
        interface T3 {
            son: any[]
        }
        interface T4 extends T1, T2, T3 {}
        let human:T4 = {
            name: 'T4',
            age: 123,
            son: []
        }
      
    • 接口继承类
    •  class Parent {
            name: string = 'name'
            getName (): string {
                return this.name
            }
        }
        
        interface P1 extends Parent {}
        
        class Child implements P1 {
            name: 'Child'
            getName () {
                return ''
            }
        }
        <!--注意: 如果是受保护成员和私有成员属性都会被接口继承过来,容易造成实现错误-->
      

泛型

  • 函数泛型定义
  •   function log<T>(val: T):T {
          return value
      }
    
  • 接口泛型定义
  •   interface log<T> {
          (value: T): T
      }
      <!--接口函数方法泛型-->
      interface log {
          <T>(value: T): T
      }
    
  • 别名泛型定义
  •   type log = <T>(value: T) => T
    
  • 泛型类
  • class Log<T> {
      run(value: T): T {
          return value
      }
    }
    <!--泛型不能约束类的静态成员-->
    <!--静态成员不能引用类类型参数-->
    class Log<T> {
      static run(value: T): T {
          return value
      }
    }
    
  • 通过接口约束泛型
  • interface Length {
      length: number
    }
    class Log<T extends Length> {
      run(value: T): T {
          return value
      }
    }
    <!--传入的值必须具有length属性-->
    new Log().run([])
    
  • 总结:
    • 泛型可以不必谢多条函数重载,冗长的联合声明,增强代码可读性
    • 灵活控制类型之间的约束

类型检查机制

  • 类型推断
    • 基础类型推断
    • let a = 1// a: number
      
    • 联合类型推断
    • let a = [1, null, '1'] //a: (string | number)[]
      let b = (x = 1) => x + 1 //b: (x?: number) => number
      
    • 上下文类型推断
    •   window.onkeydown = function (event) {
      
        }
      
    • 类型断言
    •  interface Foo {
          name: string
      }
      <!--以下断言都可以-->
      let foo = {} as Foo
      let foo = <Foo>{}
      <!--赋值-->
      foo.name = 'lk'
      
  • 类型兼容
    • 接口兼容(鸭式辨型)
    •   interface T1 {
            x: any
            y: any
        }
        interface T2 {
            x: any
            y: any
            z: any
        }
        
        let T11: T1 = {
            x: 1,
            y: 2
        }
        
        let T22: T2 = {
            x: 1,
            y: 2,
            z: 3
        }
        <!--类型少的兼容类型多的-->
        T11 = T22 //正确
        
        T22 = T11 //错误
      
    • 函数兼容(类型少的兼容类型多的)
    •  type Handler = (a: number, b: number) => void
      
        function LK(handler: Handler): Handler {
            return handler
        }
        
        let handler1 = (a: number) => a
        let handler2 = (a: number, b: number) => a + b
        let handler3 = (a: number, b: number, c: number) => a + b + c
        LK(handler1)
        LK(handler2)
        //错误
        LK(handler3)
      
    • 函数兼容-rest参数
    •   let handler1 = (a: number) => {}
        let handler2 = (a?: number, b?: number) => {}
        let handler3 = (...args: number[]) => {}
        //可选参数要想兼容,必须将tsconfig.json中的strictFunctionTypes设置为false
        handler1 = handler3
        handler2 = handler3       
      
    • 枚举兼容(枚举和number类型的兼容)
    •   enum Course {
            Tools,
            Manager
        }
        
        let course: Course.Tools = 1
        let num: number = Course.Tools       
      
    • 类兼容
    •   class Parent {
            id: number = 1
            name: string = ''
        }
        
        class Parent1 {
            id: number = 1
        }
        
        let p1 = new Parent1()
        let p = new Parent()
        p1 = p
        // 错误
        p = p1
      
    • 类兼容-私有属性
    •   class Parent {
            id: number = 1
            name: string = ''
            private age: number = 12
        }
        
        class Parent1 {
            id: number = 1
            name: string = ''
        }
        //只有子类才可以和父类实例相互赋值
        class Child extends Parent {}
        let p = new Parent()
        let c = new Child()
        
        c = p      
      
    • 泛型兼容
    •  <!--没有具体指定类型的泛型,可以相互兼容-->
        let fn1 = <T>(x: T): T => {
            return x
        }
        
        let fn2 = <U>(x: U): U => {
            return x
        }
        
        fn1 = fn2
      
  • 类型保护
    • ts能在特定区块中保证变量属于某种确定类型
    • 可以在作用域内放心使用此数据类型
    • 常用的instanceof、in、typeof就不着重讲述了,我们来看一种类型保护函数的写法
    •   class C {
            name: string = 'C'
            hello () {
                console.log('I am from C')
            }
        }
        class CPP {
            name: string = 'C++'
            hello() {
                console.log('I am from C++')
            }
        }
        <!--类型保护函数,返回值为类型谓词-->
        function isCPP (lang: C | CPP): lang is CPP {
            return lang.name === 'C++'
        }      
      
  • 联合类型和交叉类型
    • 联合类型
    •   let LH: number | string = 1
        let LH1: 'a' | 'b' | 'c' = 'c'       
      
    • 交叉类型
    •   class P {
            name: string = 'P'
        }
        class P1 {
            age: number = 123
        }
        
        let P2: P & P1 = {
            name: '',
            age: 1
        }    
      
  • 索引类型
    • 索引类型的查询操作符keyof T、T[K]、T extends U(类型约束)
    •   interface O {
            a: number
            b: string
        }
        let key: keyof O // let key: 'a' | 'b'
      
    • 类型索引和类型约束
    •   let O = {
            name: 'LK',
            age: 18,
            sex: '男'
        }
        
        function getValue<T, K extends keyof T>(o:T, k:K[]):T[K][] {
            return k.map(i => o[i])
        }    
      
  • 映射类型
    • TS内置了很多映射类型,我们先来感受一下
    •   interface T1 {
            a: string
            b: string
        }
        // 映射完成以后如下
        // type T2 = {
        //     readonly a: string;
        //     readonly b: string;
        // }
        type T2 = Readonly<T1>
      
    • 看一下内部是如何实现的
    •   //用到了我们之前讲述的索引类型
        type Readonly<T> = {
            readonly [P in keyof T]: T[P];
        }
      
    • 非同态的映射类型,会返回一个新的类型
    •   interface T1 {
            a: string
            b: string
        }
        type T3 = Record<'string' | 'number', T1>
      
    • 看一下Record的实现
    •   type Record<K extends keyof any, T> = {
            [P in K]: T
        }
      
  • 条件类型
    • T extends U ? X : Y(简单的三元类型推断)
    •       type T10<T> = T extends string ? string : T extends Function ? Function : any
      
            type T11 = T10<string>
      
            let t10: T11 = '21' 
      
    • (T | K) extends U ? X : Y
    •   type Exclude<T, U> = T extends U ? never : T;
      
    • 延迟推断 infer
    •  //这个稍微复杂点,我们可以利用上面讲到的条件类型来分析
       // 泛型 T 必须要是一个函数类型的参数
       // 泛型 T 如果可以集成于一个函数,则延迟推断返回值,预先定义一个R
       // 执行过程中,根据传入的函数类型,返回正确的R,否则any
       type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
       //看一个实际的例子
       const fn = (count: number) => count * 100
       type T1 = typeof fn
       // T2为number
       type T2 = ReturnType<T1>
      
  • namespace命名空间声明
    • 命名空间内部的变量只能在内部使用,如果想要外部使用,可以export进行导出,随着es6和commonjs规范的流行,这种命名空间方式 使用场景越来越少
    •   //a.ts
        /// <refrence path="b.ts">
        namespace LK {
            let name:string = 'lk'
            export function getName () {
                return name
            }
        }
        //这里的import和es6的不一样,这里只是针对命名空间的引用
        import getName = LK.getName
        //b.ts
        namespace LK {
            let age: number = 18
            export function getAge () {
                return age
            }
        }
      
  • 声明合并,声明合并指的是当名称一样的情况下,ts会进行合并
    • 接口自动合并,这里面还涉及到了函数重载
    •   interface TT1 {
            name: string
            fn (arg: string): string
        }
        interface TT1 {
            age: number
            fn(arg: number): number
        }
      
        let TT2: TT1 = {
            fn (arg: any) {
                return arg
            },
            name: '',
            age: 123
        }
      
    • 命名空间可以和函数、类进行声明合并
  • 编写声明文件
    • ts中声明文件一般放置于@types目录下,在package.json的配置为types下对应的位置,下例为antd
    • "typings": "lib/index.d.ts"
      
    • 声明文件以 .d.ts结尾
    •   //全局声明文件,这里声明namespace可以做到声明合并,且不污染全局声明
        //global.d.ts
        declare function Global () {}
        declare namespace Global {
            const version: string
        }
      
    • 模块声明文件
    • //module.d.ts
      declare function Module {}
      namespace Module {}
      export = Nodule
      
    • UMD模块声明文件
    • //module.d.ts
      namespace UmdModule {}
      export as namespace UmdModule
      export = UmdModule
      
    • 文件内部扩展声明
    •   declare module 'myModule' {
      
        }
        declare global {
      
        }
      
  • ts和babel结合
    • 现在前端直流打包工具webpack中集成了ts-loader和awesome-typescript-loader,但是两者各有优缺,后者更适合于babel集成 不需要装额外的类型检查工具,缺点就是支持的检查类型较少,所以我们可以采取,让ts制作语法检测,babel只做语法转换 这样,两者性能兼得
    •  // babel.config.json
       module.exports = {
           "preset": ["@babel/oreset-typescript"]
       }
       //tsconfig.json
        "noEmit": true //制作类型检查,不做语法转换