2020-03-03--typescript

2020-03-03  本文已影响0人  小丹子1o1

为什么要使用TS?

JS存在的问题:

  1. 使用了不存在的变量函数或者成员(函数名字写错等等)
  2. 函数返回类型不准确,无法预知的类型。(把不确定的类型,当成确定的类型)
  3. 在使用null或者undefined成员

js的原罪

  1. js语言本身的特性,决定了该语言无法适应大型复杂项目
  2. 弱类型,某个变量随时更换类型
  3. 解释性语言,解释一行,运行一行。错误发生的时间点是运行时。
  4. 前端开发中,大部分时间都在排错。(很多时候,都在花费时间排错!!!)

TypeScript (简称TS)

  1. TS是JS的超集,是一个可选的,静态的类型系统。
  2. 类型系统,对所有的标识符(变量、参数、函数、返回值)进行类型检查
  3. 类型检查是在编译时候,运行之前(运行的是JS代码)
  4. 需要tsc index.ts 转换才能执行,TS不参与任何运行时候的检查。

TypeScript 的常识,

  1. 2012年微软发布
  2. anders负责开发TS项目,后来开源了
  3. 定位类型检查系统,缩短项目排错时间
  4. 有了类型检查之后,无形中增强了面向对象的开发
  5. JS可以面向对象开发,但是会遇到很多问题的。(暂不举具例)
  6. 使用TS后,可以编写出完善的面向对象代码

TypeScript 的环境搭建

默认情况下,TS会做出下面几种假设:

  1. 假设当前的执行环境是浏览器的环境
  2. 如果代码中没有使用模块化语句(import、export),便认为该代码是全局执行。 一个文件中 let n:number = 1 ,tsc 编译之后形成的js文件中的变量是全局的,所有TS文件的全局变量报错。全局冲突嘛。
  3. 编译的目标代码,是ES3,可配置。

有两种方式更改以上假设:

  1. 使用tsc命令行编译的时候,加上选项参数
  2. 使用TS配置文件,来更改编译选项(重点学, tsc --init命令会直接生成tsconfig.json文件)
  3. 使用了配置文件之后,tsc编译不能再跟上文件名了,如果跟上会忽略配置文件
  4. @types/node, @types是官方的第三方库,其中包含了很多对js代码类型的描述。(比如JQ,可以安装@types/jquery, 为jquery库添加类型描述)

使用第三方库简化操作流程,编译执行。

基本的类型约束(可选的)

源代码和编译结果的差异

基本类型

  1. number
  2. string
  3. boolean
  4. 数组:let numArr:number[] = [1, 2]; 或者let numArr:Array = [1, 2],这种也叫泛型
  5. object: let u:Object; 约束力很弱,赋值函数也不会报错,用得不多。
  6. null/undefined

其它常用类型

  function throwError(msg :string):never{
    throw new Error(msg); //到这里就停了
    console.log('aaaa');  //这里以至于下面都不可能执行了
  }
   let gender:'男' | '女';       //从此只能赋值男女
   let arr:[]                    //永远只能取值为一个空数组
   let user: {
     name:string
     age:number
   }
   user = { name:'hrt', age:33 } //强力约束了

类型别名:对已知的类型定义别名

甚至还可以类型组合

   type Gender = '男' | '女'
   type User = user: {
      name:string
      age:number
      gender:Gender //这样可以很方便的维护代码,代码重用
   }

函数的相关约束

   function combine(a:number, b:number):number;
   function combine(a:string, b:string):string;
   function combine(a:number | string, b:number | string):number | string{
      if(typeof a === 'number' && typeof b === 'number'){
          return a * b
      }else if(typeof a === 'string' && typeof b === 'string'){
          return a + b
      }
      throw new Error('a和b 类型必须相同')
   }
   const result = combine("2", '2') //使用的时候,就可以具体的推导出类型

扩展类型-枚举

  1. 类型别名:type 另取名字
  2. 枚举:
   type Gender = '男' | '女'
   let gender:Gender;
       gender = '男' //这里只能选字面量的值
   //    ......如果Gender改了,gender后面甚至更多的都要改
   //根源是真实的值和逻辑含义产生了混淆,没有分开。真实值一改,全要改了。
   //字面量类型不会进入到编译结果

枚举:自定义类型,扩展类型

-  enum Gender {
     male = '男',
     female = '女'
   }
   let gender:Gender;
   gender = Gender.male
   gender = Gender.female

枚举细节规则

  1. 枚举的字段值,只能使用字符串或者数字
  2. 数字枚举的时候,值会自动自增,比如第一个写了1,后面不写,后面就自增1,
  1. 尽量不要在一个枚举中,即出现字符串字段,又出现了数字字段
  2. 使用枚举时,尽量使用枚举的名字字段,不要使用真实值,避免硬编码
  3. 能使用枚举,就不要使用类型别名,因为会出现上面我们分析的一改全改的情况

扩展:位枚举(枚举的位运算符)

    enum Permission {
        Read = 1,  //0001
        Write = 2, //0010
        Create = 4,//0100
        Delete = 8 //1000
    }
    // 1.组合权限,使用或运算
    // 0001 0010 -> 0011
    let p:Permission = Permission.Read | Permission.Write

    // 2.如何判断是否拥有某个权限, 用与运算
    function hasPermission(target:Permission, per:Permission):boolean{
      return (target & per) === Permission.Read
    }
    // 判断是不是有读权限
    hasPermission(p, Permission.Read);

    // 3.如何删除某个权限
    // 亦或,相同取0,不同1
    // 0011 0010 -> 0001 这样就可以删除写权限了;
    p = p ^ Permission.Write

接口和类型兼容性:

  1. interface,接口。TS中的接口,用于约束类,对象,函数的契约(标准)。

在代码层次的接口约束,就是强约束(java, C#)。

  1. 接口约束对象
    interface IUser {
      name:string,
      age:number,
      sayHello?:() => void, //千万不要在这里写实现,这里是定义。
    }
    let u:IUser = { //强力约束u这个字面量对象必须按照接口标准实现
      name:'hrt',
      age:18,
    }
    //那和type User 有什么区别呢?目前区别不大,在约束类中区别就大了。
    //在绝大部分场景下,约束对象尽量用接口约束,而尽量不要使用type。
    //接口和类型别名一样,不出现在编译结果中。
  1. 接口约束函数
    interface ICallBack {                  //接口约束
         (n:number): boolean
     }

    type ICallBack = (n:number) => boolean //类型别名

    type ICallBack = {                     //定界符号,具体的约束内容
        (n:number): boolean
    }
    function sum(numbers:number[], callBack:ICallBack):number{
        let s:number = 0
        numbers.forEach((d) => {
            if(callBack(d)){
              s += d
            }
        })
        return s
    }
    let addSum:number = sum([1,2,3,4,5,6], (n) => n % 2 !== 0)
    console.log(addSum)

接口是可以继承的, 可以通过继承接口来组合约束。类型别名不行了。

    interface A {
      T1:string,
    }
    interface B {
      T2:string,
    }
    interface C extends A, B { 
      T3:boolean
    }
    // 类型别名实现同样的效果:需要通过 & 符号实现,是交叉类型。
    type A {
      T1:string,
    }
    type B {
      T2:string,
    }
    type C { 
      T3:boolean
    } & A & B //C是交叉类型,本身交叉A, B

接口和类型别名的差异:

  1. 在接口中,子接口不能覆盖父接口的约束成员。
  2. 类型别名交叉的时候,相同的成员会交叉约束类型。两个成员的约束类型都会有,不覆盖。

readonly修饰符,修饰的目标是只读的

  1. 只读修饰符不参与编译,要注意修饰的目标。
    type A {
      readonly T1:string, //第一次赋值之后,不能再修改,只读
    }
  1. 比如修饰数组
    let arr: readonly number[] = [1,2,3]
    // 注意上面的修饰符号只是修饰类型。
    arr = [4,5,6] 
    // 凡是涉及改变数组的相关函数/成员的提示就没了,arr[0] = 3; 这样都不行了。只读
    arr.push()   
    // 如果 const arr: readonly number[] = [1,2,3]
    // 相当于:cosnt arr: ReadonlyAarray<number> = [1,2,3]
    // 注意,如果是修饰成员,写在成员的前面,相当于const。

类型兼容性

- 对类型的判断

      interface Duck {
          sound:"嘎嘎嘎"
          swin:() => void
      }
      let person = {
          name:'伪装成鸭子的人',
          age:11,
          // 类型断言,sound:"嘎嘎嘎" 这样的写的话TS会自动推断出sound的类型为string。
          // 用了类型断言就是"嘎嘎嘎"类型的"嘎嘎嘎"值了;其实就是更换类型。
          // 类型断言也可以在前面加<Type>来使用, <"嘎嘎嘎">"嘎嘎嘎"
          sound:"嘎嘎嘎" as "嘎嘎嘎",
          swin:() => {
              console.log(this.name)
          }
      }
      // 这就是鸭子辨型法,可以完成赋值
      // 但是,不能直接把person的字面量对象直接写过来。
      // 如果直接写过来,就更加严格了,只能是个鸭子
      let duck:Duck = person  

函数类型:一切无比的自然,但是返回值如果在约束的时候就要求返回就一定要返回

    //函数参数的鸭子辨型法
    interface ICallBack { 
        (n:number, i:number): boolean
    }
    function sum(numbers:number[], callBack:ICallBack):number{
        let s:number = 0
        numbers.forEach((d, i) => {
            // 注意这里接口约束要传两个
            if(callBack(d, i)){
              s += d
            }
        })
        return s
    }
                          //注意这里实际调用的时候根本没传完喔! 
    let addSum:number = sum([1,2,3,4,5,6], (n) => n % 2 !== 0)
    // js的高阶函数forEach, map, filter都是这样的自然

类(面向对象思想、是一种思维模式)

  1. 属性的初始化检查,"strictPropertyInitialization": true //更加严格的属性检查,检查初始化
  2. 构造函数中检查有没有赋值,或者属性列表有没有初始化默认值
  3. 属性可以修饰为可选的,用问号?
  4. 有些属性初始化之后就不能改变了, 用 readonly 修饰
  5. 有些属性是不希望外部能读取的,使用访问修饰符。可以控制类中的某个成员的访问权限

感受一下下面的实例:

   // "strictPropertyInitialization": true  更加严格的属性检查,检查属性初始化
   class name {
       //属性列表
       readonly id:number;              //只读, 相当于const
       gender:'男' | '女' = '男'
       public pid?: string
       private publishNumber:number = 3 //每天一共可以发多少篇文章
       private curNumber:number = 0     //当前可以发布的文章数量
       // 可以在形参中加修饰符达到初始化的效果。简化繁琐代码
       constructor(public name:string, public age:number) {
           this.id = Math.random()
       }
       public publish(title:string){
         if(this.publishNumber > this.curNumber){
           console.log('发布了一篇文章', title)
           this.curNumber++
         }else{
           console.log('不能发布文章了')
         }
       }
   }
   const user = new name('hrt', 18)
   user.publish('文章1')
   user.publish('文章2')
   user.publish('文章3')

访问器,用于控制属性的读取和赋值

    class name {
        // 可以在形参中加修饰符达到初始化的效果。简化繁琐代码
        constructor(public name:string, private _age:number) {
            this.id = Math.random()
        }
        // 利用private控制,getAge() 是java的做法
        // C# 是这样做的 get age,用的时候就像普通对象的获取
        // ES6 的时候,也是这样玩的,其实这样也相当于是readonly
        // 规范,写私有属性的时候尽量前面加下划线
        get age(){
            return this._age
        }
        set age(age: number){
            this._age = age
        }
    }
    const user = new name('hrt', 18)
    user.publish('文章1')
    user.publish('文章2')
    user.publish('文章3')
    user.age = 10

泛型,重点类型后面会频繁使用

在函数中使用泛型:

  1. 在函数名之后写上两个尖括号加泛型名称
  2. 泛型就相当于类型变量,一般用T表示,定义的时候不知道什么类型,运行的时候才能确定是什么类型
  3. 执行语句如果不使用指定泛型的类型,take([1, '2', 3], 2),就不会报错了。
  4. 因为TS可以类型推导,很多时候,TS可以智能的推导出类型,前提是使用了泛型。
  5. 如果无法完成推导,并且有没有传递具体的类型,默认为空对象(相当于any)
  6. 定义时候泛型可以使用默认值,不指定就是默认值。如:take<T = number>
  7. 泛型是一个地方确定类型了,就可以推导确定出具体的类型了。
  // T表示泛型,依附于这个函数,泛型就相当于类型变量
  function take<T>(arr:T[], n:number): T[]{
      if(n >= arr.length){
          return arr
      }
      const res: T[] = []
      for (let i = 0; i < n; i++) {
          const element = arr[i];
          res.push(element)
      }
      return res
  }
  // 调用函数的时候才知道什么类型
  // 这样就能把丢失的信息(类型)找回来了
  take<number>([1, '2', 3], 2) //指定类型的话,里面有字符串类型,就会报错;

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

多泛型

模块化

  1. TS中如何书写模块化语句
  1. 编译结果中的模块化

小总结:

  1. 基本类型:boolean number string object Array void never null undefined
  2. 字面量类型:具体的对象,或者元组
  3. 扩展类型:类型别名,枚举,接口,类。(实际上还有很多高级的联合类型)
  4. 类型别名和接口不产生编译结果,枚举和类产生编译结果。(枚举产生的就是类,类没啥区别)
  5. TS类:访问修饰符,readonly, 一些访问修饰符(public等)
  6. 泛型:解决某个功能和类型的耦合。(其实就是抽出一个通用的方法,方便代码重用)
  7. 类型兼容性:鸭子辨型法,子结构辨型法。(A如果想赋值给B,A必须满足B的结构,A的属性可以多不可以少)
  8. 类型兼容性,在函数类型兼容的时候,参数是可以少的,但是不可以多。要求返回必须返回,不要求你随缘
  9. 类型断言:开发者非常清楚某个类型,但是TS分辨不出来,可以用类型断言 as
  10. TS有很多的内置关键类型,像ReturnType之类的,用到时候可以查文档
  11. TS 还有 keyof、is 之类的关键字,用到时候也可以多查阅文档

深入理解类和接口

里氏替换原则

继承的作用

  1. 继承可以描述类与类之间的关系,如A和B

成员的重写(override)

protected private public

继承的单根性和传递性

抽象类

静态成员 static

再谈接口

    interface IDown{
      down():void
    }

    class Children extends Parent implements IDown{
        down(): void {
            throw new Error("Method not implemented.");
        }
    }

类型保护函数:相当于 a instanceof IDown, java中可以这么用TS不行。

function isHasIDown(chi: object): chi is IDown {
  if((chi as unknown as IDown).方法){
    return true
  }else{
    return false
  }
}
    class A {
        name: string = 'A';
    }
    class B {
        age: number = 1;
    }

    interface C extends A, B{}

    let c:C = {
        name:'C',
        age: 10
    }

索引器

对象[值]

  class A {
      [prop:string]:string
      name:string = 'hrt'
  }
  class A {
      [prop:string]:string
      name:string = '1'
  }

  let c:A = new A()
  c.name = "ss"
  c.x = '2222'

类this指向问题

  class A {
    name:string = 'hrt'
    say(){
        console.log(this.name)
    }
  }
  let c:A = new A()
  let s = c.say
  s()
  let c = {
      name: 'hrt',
      say(){
          console.log(this.name) //this是any类型的
      }
  }
  let s = c.say
  s()
  interface IUser {
    name:string,
    age:number,
    sayHello(this:IUser):void //强行约束this
  }
  let c:IUser = {
      name: 'hrt',
      age:18,
      sayHello(){
          console.log(this.name)
      }
  }
  let s = c.sayHello
  s() //报错

react + ts 结合会有很多的坑!!!

  1. 某个组件有哪些属性需要传递?(antd 官方文档)
  2. 某个组件有某个属性,需要传具体的属性类型
  3. 传递事件的时候,具体需要传的参数
  4. 错误发生在运行时
上一篇 下一篇

猜你喜欢

热点阅读