TypeScript(上) 语法/应用示例

75 阅读8分钟

一、TS基础

1.TypeScript的本质

  1. 是JS的超集,添加了注解的JavaScript,原有基础上
  2. ts是在开发阶段,生产都会转成js
  3. 是强类型语言吗 -- 不是; 强类型语言,数据类型不能隐式转换; ts可以,只是多了一层校验

2.基础类型写法

  1. let num: number = 1;

    JS原始类型:number,string,boolean,null,undefined,symbol

  2. 数组类型

    let array1: number[] = [1,2,3]; //推荐写法 ,避免与JSX代码冲突,减少代码量
    let array1: Array<number> = [1,2,3] //Array泛型
    let array2: string[] = ['x','y']
    
  3. 元组类型Tuple

  • 限制数组元素的个数和类型,适合实现多值返回

3.特殊类型

  1. any 可以绕过静态类型检测的一个作弊方法

可以把任何类型赋给any, 也可以把any赋给任何类型(never除外)

 let anything:any = {}
 //以下错均不会报错
 anything.do() 
 anything = 1;
 let num: number = anything

any类型的任意属性也是any

 let z = anything.x.y.z //z类型也是any
 z(); //不会报错

2. unknown 描述类型并不确定的变量

 let result:unknow;
 if(x){ result = x(); }
 else if(y){result = y(); }
  • unknow相比any更安全,unknown只能赋值给unknown和any

  • 但如果不缩小类型,对unknown执行操作会报错

    let result: unknown;
    result.toFixed(); //提示ts(2571);
    
  1. void, 表示没有返回值的函数
  2. undefined,最大价值体现在接口类型上,表示可缺省,未定义的属性
  3. null主要体现在接口制定上,表示对象或属性可能是空值
  • null/undefined也具备警示作用,容错处理
  1. never 永远不会返回值的类型

    抛错的函数
    function ThrowError(msg: string): never{
       throw Error(msg);
    }
    

never可以给所有类型赋值,但其他类型不能给never类型赋值

   const props: {
      id: number,
      name?: never
   }
   这里实现了禁止写name属性,给name赋值就会报错

7.object: 表示非原始类型的类型(没什么用武之地)

   declare function create(o: object | null):any;
   create({});//可以
   create(()=> null);//可以
   create(2)// 报错  

4.类型断言

TypeScript类型检测无法做到绝对智能

  const num: number[] = [1,2,3,4];
  const num2: number = num.find(item => item > 2);
  // 报错 因为可能是数字,也可能是undefined
  1. as语法做类型断言,相当于强制类型转换,结果一定是这个类型

    const num2: number = num.find(item => item > 2) as number
    
  2. 变量/值后面加 ! ,表示非空,排除null, undefined

    const a :number | undefined = undefined;
    const b: number = a!;
    
  • 使用的意义:告知编译器,运行时会有值

5.类型推断

类型标注后置,意味着编译器可以通过代码上下文推导类型

 let x1 = 42; //推断出x1是number
 let x2: number = x1; //ok

字面量类型

 let str: 'string1' = 'string1';
 let str2: string = 'string2';
 
 str2 = str; // ok
 str = str2 //报错

“马”比喻string类型,“黑马”比喻‘string1’类型;黑马肯定是马,但马不一定是黑马

  1. 字面量类型:限制函数的参数为指定的字面量类型集合

    type Direction = 'UP'|'DOWN';
    function move(dir: Direction){...}
    
    interface Config{
      size:'small'|'big',
      margin:0|2|4,
      inEnable: true|false //和用boolean没有区别
    }
    

字面量类型拓宽

通过let,var定义的变量/形参/属性,指定了初始值并且 没有显示添加类型注解; => 字面量类型拓宽:指定的字面量类型拓宽后的类型

  let str = 'string'; //类型是string
  let fun = (str='aaa') => str; 
  //(string) => string

字面量类型使用

function handle(url:string, method: 'GET' | 'POST'){}
const req = {
   url:'http://...',
   method: 'GET'
}
handle(req.url, req.method) //req.method会报错,req.method会被推断为string类型
const req = {
   url:'http://...',
   method: 'GET'
} as const; 

类型缩小

  1. 类型守卫, 可以将函数参数的类型从any缩小到明确类型

    fun(param : any){
       if(typeof param === 'string'){
          return ...          
       }
       else if(typeof param === 'boolean'){
          return ...
       }
    }
    其他类型判断可以使用 in,instanceof
    
  2. 处理联合类型场景

  3. 真值缩小 if(str)... ; !!str ; Boolean(str)

函数类型

type Adder = (a: number, b: number) => number; //ts函数类型定义
function print(fn: Adder){
   fn(a,b);
}
const add: Adder(a,b)=>a+b;

函数返回值类型

  • 可以没有显示return,此时函数返回值undefined;

  • 但不能显示设置函数返回值为undefined类型,应该用void类型标识没有返回值的函数

  • 可以不显示设置类型,函数会根据内容推断出返回类型

     interface Enity{
        add: (a: number, b number) => number
     }
     
     const obj:Enity = {
        add:(a,b) =>(a+b);     
      }
    

函数参数类型

  1. 可选参数和默认参数

    function log(x?:string){...}
    //参数可以不传
    
    function log(x:string | undefined){...}
    //参数一定要传,类型必须是string 或 undefined
    
  2. 剩余参数,ES6可以把多个变量收集到一个变量中

    function sum(...nums: number[]){
        return nums.reduce((a,b)=>a+b, 0)
    }
    sum(1, 2, 3);
    sum(1, '1'); //报错
    
  3. 参数值出现在上下文的时候,可以不定义类型

    const name = [1,2,3];
    name.forEach(function(item){ //item为name数组中的元素,可以推断出类型
         console.log(item);
    })
    

Interface 接口

仅限于描述/约束对象,只有规范不实现

  1. 接口定义
interface language{
   name: string;
   age?: () => number; //?表示属性可缺省
   // age: () => number | undefined不等价
   [key: string]: any //可以自己增加额外的属性   
}
      
  使用可能缺省属性的时候 可以类型守卫 / Optional Chain
  if(typeof XX.age === 'function'){
  XX.age();
}       
XX.age?();
  1. 定义函数类型(很少使用)

    interface lan1{
       (param: language): void
    }
    let study1: lan1 = param => {...}
    
    //内联类型 定义函数类型
    type study = (param: language) => void //推荐写法
    
  2. 接口类型可以继承/被继承

接口类型的作用:将内联类型抽离出来,从而实现类型可复用
interface PayInterface{
   handle(price:number): void{}
}
class Alipay implements PayInterface{
  public handle(price:number): void{
     console.log('zfbfk')
  }
}
class Wepay implements PayInterface{
  public handle(price:number): void{
     console.log('wxfk')
  }
}

Type 类型别名

type用来给类型起一个新名字,方便重复使用;可以是基本类型,对象,联合,交集

   type username = string;
   type userMsg = string | number;
   type Person{
       name: username,
       user: userMsg
   }
   type lantype {
      name: string;
      age: () => number;
   }

interface和Type区别

  1. interface重复定义的接口类型,可以实现属性叠加,方便拓展;

  2. type重复定义类型别名会报错,

    interface Animal{
       name: string
    }
    interface Bear extends Animal{
       honey: boolean
    }
    const bear: Bear = {
       name: 'aaa'
       honey:true
    }
    ------------------
    type Animal = { name: string }
    type Bear = Animal & { honey: boolean }
    
    
    

联合和交叉类型

  1. 联合类型 |,数据类型不唯一的情况

    function fun(size: string | number, unit: 'px'|'rem'){}
    也可以用类型别名抽离,再联合
    type unit1 = 'vh'|'vw';
    type unit2 = 'px'|'rem';
    unit1|unit2
    
  2. 交叉类型 &,多个类型合并成一个

    type useless = string & number; // never 无用
    
  • 用途:合并接口类型(求并集) type inter = {id: number} & {age: number}

枚举类型

  1. 定义

    enum Day{
       Sunday,
       Monday
    }
    
  • JS中没有与枚举类型对应的 原始实现,会把枚举类型转译为,属性为常量,值从0递增的对象

    //使用
    work(Day.Sunday);
    work(0);
    
  • 数字枚举 仅指定常量命名,定义的就是从0开始的数字集合

    enum Day{
      Sunday = 1, //指定初始值
      Monday
    }
    
  • 枚举类型-字符串枚举

      enum Day{
      Sunday = 'Sunday',
      Monday = 'Mnday'
    }
    
  • 枚举类型-异构枚举(鸡肋) 支持枚举类型同时拥有数字,字符类型

泛型

  1. 定义:类型 是一个参数,将原来具体的类型进行参数化,即 把参数param的类型定义为一个参数,而不是一个明确的类型,等函数调用时再传入明确的类型

    function reflect<P>(param: P){ // <P>定义一个泛型P,指定param参数类型为P
        return param;
    }
    const str1 = reflect<string>('string'); 
    //const str1 = reflect('string'); //参数有传值,类型可省
    const str2 = reflect<number>(1);
    
  2. 目的:约束类型成员之间的关系(函数参数和返回值,类/接口成员和方法之间的关系)

  • 泛型约束

    function rel<P extends number | string | boolean>(param: P):P{
      ...//限制函数传参指定的几个类型
    }
    

限制传入的参数,必须有length属性 -- 需要继承实现

function longer<Type extends { length: number}>(a: Type, b: Type){
  if(a.length > b.length){ return a; }
  else{ return b; }
}
也可以
Type extends string | any[]

3. 泛型和类结合

   class Collection<T>{
     data: T[] = [];
     public push(...items: T[]){ this.data.push(...items)}
     public shift(): T{ return this.data.shift() }
   }
   type User = { name: string; age:number }
   const user1 = { name: 'aaa', age: 23 }
   const coll = new Collection<User>()
   collections.push(user1)

4. 接口使用泛型

   interface Artical<B,C>{
      title: string
      isLock: B
      comments: C[]
   }
   type Comment = {
      content:string
      author:string
   }
   const aa: Artical<Boolean, Comment> = {
      title:'ts网站',
      isLock: true,
      comments: [{content:'评论', author:'作者'}]
   }

装饰器 -decorator

  • 配置ts装饰器环境
 "experimentalDecorators": true,  /* Enable experimental support for legacy experimental decorators. */
 "emitDecoratorMetadata": true, 

类装饰器

想使用一个类,并且增加一个方法,但又不影响这个类本身,就可以在自己要使用的地方,加一个装饰器

  function add(target: Function): void{
     target.prototype.startClass = function(){
       ...增加一个函数
     }
  }

  @add
  class Course{
    constructor(){
      ...
    }
  }

装饰器语法糖,@add 相当于 add(Course)

例:可以用装饰器给不同的类统一添加方法/属性

const MessageDecorator: ClassDecorator = (target: Function) => {
    target.prototype.message = (content:string) =>{
       console.log(content);
    }
}
@MessageDecorator
class LoginControl{
  public login(){
     this.message('登陆成功')
  }
}
@MessageDecorator
class Music{}
new LoginControl().login()

方法装饰器

const showDec: MethodDecorator= (...args: any[])=>{
    // args[0].name = 'aaaa'
    console.log(args)    
  [{ name: 'aaaa' },  args[0] 为函数的原型对象 //如果是静态方法,args[0]为构造函数
  'show',             args[1] 函数名称
  {                   
    value: [Function: show],
    writable: true,
    enumerable: false,
    configurable: true
  }
]
}
class User{
   @showDec
   public show() {}
}

例:装饰器工厂,可以在使用装饰器的时候传参数

// 装饰器
 const showDec: MethodDecorator= (...args: any[])=>{
     const method = args[2].value;
     args[2].value = ()=>{
        setTimeout(()=>{
            method()
        },2000)
     }
   }
   //装饰器工厂
   const SleepDecorator = (times: number): MethodDecorator =>{
     return  (...args: any[])=>{
        const method = args[2].value;
        args[2].value = ()=>{
           setTimeout(()=>{
               method()
           },times)
        }
      }

例:ts装饰器全局异常管理

const ErrorDec: MethodDecorator = (target:object,propertyKey: string,descriptor:PropertyDescriptor) => {
   const method = descriptor.value;
   descriptor.value = () => {
     try {
       method()
     }catch(error:any){
       console.log(error.message)
     }
   }
}
class User{
  @ErrorDec
  find(){
     throw New Error('asd');
  }
}

原理解析

  1. 源码输入

    let a: number = 2;
    
  2. scanner扫描 => 识别内容生成数据流

    {
       'let': 'keyword',
       'a': 'identifier',
        '=': 'assignment',
       '2': 'integer',
       ';': 'eos' (end of statement)
    }//没有number
    //number 是用来做检测的
    
  3. 解析器 parser => 生成语法树AST

  4. 绑定器 创建symbols 上面的AST对应的每个节点,node.symbols

  5. 校验器 checker,检查TS语法错误

  6. 发射器 emitter根据每个结点的检查结果,翻译成js