2020-03-03--typescript

为什么要使用TS?

  • 获得更好的开发体验,解决JS中一些难以解决的问题

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 的环境搭建

  • 在node中搭建TS开发环境,因为要关注语法本身

默认情况下,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库添加类型描述)

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

  • ts-node: 将ts代码在内存中完成编译,同时完成运行。
  • 使用的时候要指定一个入口,命令 ts-node src/index.ts, 开发阶段很好用。
  • 但是不能监控代码变化,要使用nodemon:用于检测文件的变化。
  • nodemon --exec ts-node src/index.ts //检测文件变化,变化时候执行ts-node命令
  • nodemon --exec ts-node src/index.ts 把这个命令写成一个脚本,方便执行。
  • 把命令写到package.json,中。"scripts":{ "dev" : "nodemon --exec ts-node src/index.ts" }
  • 启动的时候直接npm run dev就可以了
  • nodemon 监控的文件太多了,所以要配置一下它:
  • nodemon -e ts --exec ts-node src/index.ts // -e ts 文件扩展名ts
  • 进一步配置,监控的文件夹:nodemon --watch src -e ts --exec ts-node src/index.ts
  • 开发完成直接tsc ,打包输出dist

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

  • 如何进行类型约束?
  • 变量,函数的参数、函数的返回值
  • f2或者重命名符号,可以全局改一个索引。f12或者转到定义。就可以快速跳到定义。
  • 很多场景下ts可以类型推导,

源代码和编译结果的差异

  • 运行过程中就是JS代码,切记,TS类型是静态类型,暂时做不到运行时的类型检查

基本类型

  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
  • null 和 undefined 是所有其它类型的子类型,它们可以赋值给其它类型
  • "strictNullChecks": true, 可以更加严格的空类型检查,不让null 和 undefined 随便赋值,只能赋值给自身
  • 随着版本更新,2020-2-20发布的3.8版本有更多新特性

其它常用类型

  • 联合类型(多种类型,任选其一),比如字面量联合类型,类型联合类型,后面还会学到 & 与操作

  • let name:string | undefined = undefined, 如果没有具体赋值,还是可以类型推导。

  • 类型保护,如果某个变量进行判断之后,就可以确定是什么类型了。(后面讲怎么触发类型保护,只能触发原始类型)

  • void类型: 通常用于约束函数的返回值

  • never类型:通常用于约束函数的返回值,表示该函数永远不可能结束,不会返回值

  • 举例:或者函数内死循环。

  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 } //强力约束了
  • 元祖类型(Tuple):一个固定长度的数组,并且数组中每一项的类型确定

  • let tu:[string, number]; //之后赋值必须赋值两项,类型确定

  • any类型:any类型,可以绕过类型检查,因此,any类型的数据,可以赋值给任意类型

  • 举例:let data:any;

  •   let num:number = data
    
    
  • 会有隐患,不要随意用

  • 基本类型和其它常用类型统称为TS类型

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

  • 这个react会经常用到
  • type User = user: { name:string age:number }
  • let user: User //不仅变量可以这样方便的使用,函数返回值也可以方便点,简洁代码
  • 后面还可以通过一些操作符,像&之类的,高级联合类型

甚至还可以类型组合

   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') //使用的时候,就可以具体的推导出类型
  • 可选参数:在某些参数后面加上问号,代表可选参数,不仅在函数(要放末尾)中可以使用,其它地方也可以
  • 函数默认值:像ES6 一样可以直接在参数上面写默认参数

扩展类型-枚举

  • 枚举类型可以很好的避免硬编码
  1. 类型别名:type 另取名字
  2. 枚举:
  • 枚举通常用于约束某个变量的取值范围,用字面量和联合类型配合使用,也可以达到同样的目标
  • 字面量类型的问题:会出现重复的代码,比如 let gender: '男' | '女';
  • 在某个函数中要使用通过性别查找,函数的形参,就要写字面量;这样就不够灵活,出现硬编码
  • 解决:使用type类型别名,就可以解决了,但是,如果一旦别名类型一改,全部的代码都要改
  • 举例:
   type Gender = '男' | '女'
   let gender:Gender;
       gender = '男' //这里只能选字面量的值
   //    ......如果Gender改了,gender后面甚至更多的都要改
   //根源是真实的值和逻辑含义产生了混淆,没有分开。真实值一改,全要改了。
   //字面量类型不会进入到编译结果

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

  • 枚举类型可以很好的解决上面硬编码的问题
  • 定义:enum 枚举名{ 枚举字段 }
  • 举例使用:
-  enum Gender {
     male = '男',
     female = '女'
   }
   let gender:Gender;
   gender = Gender.male
   gender = Gender.female
  • 因此,使用的时候是使用逻辑名称,赋值的时候还是字面量
  • 如果逻辑名称改了,f2重构就可以了,一改全改,编码硬编码
  • 枚举参与编译,会出现在编译结果中,编译成一个对象。以前的类型别名,不会参数到编译中的

枚举细节规则

  1. 枚举的字段值,只能使用字符串或者数字
  2. 数字枚举的时候,值会自动自增,比如第一个写了1,后面不写,后面就自增1,
  • 如果全都不写,第一个就是0,后面自增 enum Level { level1 = 1, level2, level3, }
  • 被数字枚举约束的变量,可以直接赋值为数子,如:let lev:Level = 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中的接口,用于约束类,对象,函数的契约(标准)。
  • 举例,电源接口,如果两个充电器的电源接口满足相同的标准,就可以互相拿来充电。
  • 只不过,我们以前前端开发的契约标准就是文档形式的。API文档,就是一个契约文档。
  • 但是文档作为契约,作为标准的话,是一个弱标准。写代码的时候调用接口时候会写错,没有提示。

在代码层次的接口约束,就是强约束(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。

类型兼容性

  • 两个类型,如果能完成赋值,A = B,则 B和A兼容
  • 用得是鸭子辨型法(子结构辩型法):
  • 目标类型(A)约束有某些特征,赋值的类型(B)只要满足该特征即可,可以有多于的属于自己的。

- 对类型的判断

  • 基本类型:完全匹配
  • 对象类型:鸭子辨型法
  • 举例:
      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都是这样的自然

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

  • 基础部分,仅考虑新增的语法部分,后面再讨论面向对象的思想
  • 使用属性列表来描述,不能像以前JS一样动态的赋值增加
  1. 属性的初始化检查,"strictPropertyInitialization": true //更加严格的属性检查,检查初始化
  2. 构造函数中检查有没有赋值,或者属性列表有没有初始化默认值
  3. 属性可以修饰为可选的,用问号?
  4. 有些属性初始化之后就不能改变了, 用 readonly 修饰
  5. 有些属性是不希望外部能读取的,使用访问修饰符。可以控制类中的某个成员的访问权限
  • public 默认是这个访问权限,公开的,所有都可以访问
  • private 私有的,只有在类中使用
  • protected 受保护的,暂时不考虑
  • 可以在构造函数的形参增加一个访问修饰符,可以这样简写。达到传参赋初值效果(不做任何操作的初始化赋值才可以)

感受一下下面的实例:

   // "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) //指定类型的话,里面有字符串类型,就会报错;

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

  • type callback = (n:number, i:number) => boolean

  • type callback = (n:, i:number) => boolean

  • 类:通过约束类的泛型,类下面的函数成员等,就直接是根据类实例化的时候的泛型

  • 接口和函数也差不多

  • 泛型,其实就是泛指的类型,也就是不能暂时确定的类型。

  • 泛型约束:函数使用泛型,参数是泛型,返回值也是泛型, 对T进行一些约束。, 这样子一约束之后,到时候传过来的类型,必须满足XXX,鸭子辨型法。

多泛型

  • 在ts+react中的类型声明文件有很多用例,注意实践多练多查

模块化

  • 前端领域的模块化标准:ES6,commonjs/amd/cmd/umd/system/esnext
  • 前两个是讨论的重点
  1. TS中如何书写模块化语句
  • TS中,导入和导出,统一使用es6的模块化标准
  • 尽量不要使用默认导出,因为没有智能提示 export default。应该用export
  • 导入的时候不要添加后缀名
  1. 编译结果中的模块化
  • 如果编译标准是ES6,没有区别
  • 如果编译结果的模块化标准是commonJS,导出的声明,会变成exports的属性

小总结:

  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 之类的关键字,用到时候也可以多查阅文档

深入理解类和接口

  • 面向对象:以类为切入点进行编程。(可以产生对象的模版)
  • 人:
  • 特征(属性):眼睛,鼻子,嘴巴四肢,性别
  • 动作(方法):说话,运动,制造工具
  • react:类组件,可以产生对象的模版
  • 如何学习?
  • TS的OOP(Oriented Object Programing)
  • 开发小游戏很锻炼思维,多实践

里氏替换原则

  • 类型匹配:
  • TS 用的是鸭子辨型法。子类的对象,始终可以赋值给父类;在面向对象中,这种现象叫做里氏替换原则。
  • 如果要判断具体的子类型,用 instanceof 即可

继承的作用

  1. 继承可以描述类与类之间的关系,如A和B
  • B是父类,A是子类
  • B派生A,A继承自B
  • B是A的基类,A是B的派生类
  • A继承自B,会拥有B的所有方法和属性
  • 子类的对象,始终可以赋值给父类。(鸭子辨型法)
  • 实例:
  • let a:B = new A()
  • a变量中的this指向还是正常的,还是执行A的实例
  • 此时a只能使用子类和父类共有的方法,因为你声明的是B父类。就是A子类有多余的都不能使用,一般不会这么奇葩写
  • instanceof 判断具体是哪个子类型。

成员的重写(override)

  • 子类中覆盖父类的成员,这就叫重写
  • 重写不能改变父类成员的类型(属性)
  • 重写方法的时候,子类参数要和父类一样,返回值可以不一样,但差别太大就没必要使用同一个方法
  • 重写方法的时候,要注意this指向
  • super:在子类的方法中,可以使用super读取父类的成员。super和this是有区别的。

protected private public

  • 编译结果没有访问修饰符
  • readonly:只读修饰符
  • protected:受保护成员,只能在自身或者子类使用
  • private:私有的,只能自己使用
  • public:公共的,默认的

继承的单根性和传递性

  • 单根性:每个类最多只能拥有一个父类。
  • 传递性:如果A是B的父类,并且B也是C的父类,则A也是C的父类

抽象类

  • 为什么需要抽象类?
  • 中国象棋为例子,游戏里面存在各种棋子,棋子对象是抽象的,兵、马、炮这样的才是具体的实例
  • js 无法描述抽象类,TS可以
  • abstract class Chess { }
  • 有时某个类只是表示抽象的概念,用于提取子类的公有成员,而不能直接创建它的实例对象。
  • 该类可以作为抽象类,主要是为了解决代码重复的问题。
  • 抽象类只能用于继承,不能创建实例。抽象类是一个强约束,用于对类的强约束。
  • 如果抽象类中某个成员必须要子类实现,也可以使用abstract来进行强约束,要求子类一定要实现。
  • 抽象成员必须出现在抽象类中。
  • 抽象类可以再次继承抽象类,抽象类中可以先实现抽象成员,也可以到具体的子类再实现。
  • 这样的强约束,对于维护和团队合作是非常好的。

静态成员 static

  • 属于某个构造函数的成员, 不附属在实例上,就要加上static关键字
  • 静态方法中的this, 执向的是当前类

再谈接口

  • 接口用于约束类,对象、函数,是一个类型契约。
  • 在类的实例中,方法没有强约束力。容易将类型和方法耦合在一起。
  • 例如:A, B 继承自C。A,B有各自的方法,在某处要一起调用,就需要instance判断类型了。
  • 系统缺失对能力(方法)的定义 - 解决办法(接口)
  • 面向对象领域中的接口的语义:表达了某个类是否拥有某种能力(方法)
  • 某个类具有某种能力,其实,就是实现了某种接口。implements interface(可以实现多个接口)
  • 如果implements实现了某种能力,必须要实现,不实现会报错。
  • 例子:
    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指向问题

  • TS类中其实是使用了严格模式的,下面这样用会导致this为undefined
  class A {
    name:string = 'hrt'
    say(){
        console.log(this.name)
    }
  }
  let c:A = new A()
  let s = c.say
  s()
  • 而如果使用字面量的形式,this是any类型的
  let c = {
      name: 'hrt',
      say(){
          console.log(this.name) //this是any类型的
      }
  }
  let s = c.say
  s()
  • TS允许在书写函数的时候,手动声明该函数的this指向,
  • 将this作为第一个参数,只用于约束this,不是真正的参数,也不会出现在编译结果中。
  • 接口强约束了this指向,防止this乱指向,压根就犯不了错误。
  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. 错误发生在运行时
  • 可以通过propsTypes约束属性的类型,但是错误发生在运行时。
  • ts都可以解决这些问题(F12看定义的声明文件,需要有一定的TS基础)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容