Typescript学习笔记

130 阅读11分钟

一、数据类型

1.any和unknow

any类型可以赋值给任意类型,会造成类型污染,所以不建议使用,而unknow类型不能赋值给其他类型,除非使用类型断言,不会造成类型污染,因此在不确定类型时建议使用unknow而不是any。

let a:any;
a = 1;
let b:string;
b = '1';
console.log(typeof b)
b = a; //此处b的类型被污染
console.log(typeof b)
a = true;
b = a; //此处b的类型被污染
console.log(typeof b)
let c:unknown;
c = 321;
b=c as 'string'; //只有使用类型断言才能用unknow进行赋值
b = <string>c; //类型断言的另一种方法
console.log(b,typeof b) //只有使用类型断言才会导致b的类型被污染

2.void和never

void代表空值,返回值可以为空或undefined但不能为null,null本身也是一种数据类型

function fn():void{
    return; 
}

never类型表示永远不会有值,函数内部只能为抛出错误,或永远无法结束的死循环

function fn1():never{
    throw new Error('这是一个错误');
}
function fn2():never{
    while(true){}
}

3.object

{}用来指定对象中而已包含哪些属性,语法为{属性名:属性值,属性名:属性值},在属性名后边加上?,表示属性时可选的,函数的参数后面也可以加上?,表示参数可选

let a:{name:string,age?:number};
a = {name:'yuuri',age:17};

[propName:string]:any 表示任意类型的属性,[]内表示后续可以传的多个符合命名规则值的集合,此处propName可随意填写,但建议写propName

let a :{name:string,[propName:string]:any}; //表示只有name属性是必须的,其他的属性都是可选的
a = {name:'yuuri',a:1,b:'2',c:true};

设置函数结构的类型声明,语法:(形参:类型,形参:类型...)=> 返回值类型,不定参使用...解构并限制数组类型

let a:(a:number,b:number)=>number;
a = function(a:number,c){
    return 123
}
//不定参
let b = (a:number,b:number,...rest:Array<any>):boolean=>{ //设置不定参的数据类型
    return false
}
b(1,2,3,4,5)

4.array

let a:string[];
a = ['1','2'];
let b:Array<string|number>; //用此方法可以让数组中包含多种类型数据
b = ['1',2];

5.元组

元组就是固定长度的数组,语法[类型,类型,类型]

let a:[string,number];
a = ['a',1];

6.枚举enum

若枚举中不给具体值,则值从0开始自动向上递增

enum Gender {
    male=1,
    female=0
}
let a  = {name:'yuuri',gender:Gender.female}; //此处gender会赋值为0
console.log(a);

7.其他tips

& 表示同时满足

let a :{name:string}&{age:number}&{[propName:string]:any};
a = {name:'yuuri',age:17,gender:0};

| 表示满足其中之一即可

let a:1|2|3;
a = 1;
a = 2;
a = 3;

类型的别名

type myType = 1|2|3|4|5;
let a:myType;
a = 3;

二、tsconfing.json配置

此处以vue-cli搭建的vue3项目中的tsconfing.json代码片段说明

{
  "compilerOptions": {
    "target": "esnext",//指定ts编译版本,默认ES3
    "module": "esnext",//指定要使用的模块化的规范
    "strict": true,//所有严格检查总开关
    // "alwaysStrict": false,//用来设置编译后的文件是否使用严格模式,默认false
    // "noImplicitAny": true,//不允许隐式的any类型
    // "noImplicitThis": true,//不允许不明确类型的this
    "jsx": "preserve",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "allowJs": true,//是否对js文件进行编译
    "checkJs": false,//是否检查js代码是否符合语法规范,一般与allowjs的值一致,默认为false
    "removeComments": false,//是否移除注释
    "noEmit": false,//不生成编译后的文件,默认为false,只编辑器检查语法
    "noEmitOnError": false,//当有错误时不生成编译后的文件
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "useDefineForClassFields": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [  //用来指定项目中使用的库
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ],
    // "outDir": "./dist", //用来指定编译后文件所在的目录
    // "outFile": "./dist/app.js", //将代码合并为一个文件,设置outfile后,所有全局作用域中的代码将会合并到一个文件中
  },
  "include": [  //需要编译的目录,**表示任意目录,*表示任意文件
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [  //不参加编译的目录
    "node_modules"
  ],
  // "files": [ //只编译指定文件
  //   "src/xx.ts"
  // ],
}

三、类

直接定义的属性是实例属性,需要通过对象的实例去访问
使用static开头的属性是静态属性(类属性),可以直接通过类去访问
readonly开头的属性表示只读属性,无法修改
如果方法以static开头则方法就是类方法,可以直接通过类去调用

class Person{
  //类
  /*
    直接定义的属性是实例属性,需要通过对象的实例去访问
      const per = new Person();
      per.name;
    使用static开头的属性是静态属性(类属性),可以直接通过类去访问
      Person.age
    readonly开头的属性表示只读属性,无法修改
  */
  name:string='孙悟空';
  static readonly age:number = 18;
  /* 类方法
    如果方法以static开头则方法就是类方法,可以直接通过类去调用
  */
 static sayHello(){
  console.log('hello everyone');
 }
}
const per = new Person();
console.log(Person.age);

1.构造类

class Dog{
  name:string;
  age:number;
  // constructor被称为构造函数
  // 构造函数会再对象创建时调用
  constructor(name:string,age:number){
    //在实例方法中,this就表示当前的实例
    //在构造函数中当前对象就是当前新建的那个对象
    //可以通过this向新建的对象中添加属性
    // console.log(this);
    this.name = name;
    this.age = age;
  }
  static bark(){
    alert('汪汪汪');
  }
}

const dog1 = new Dog('小黑',4);
const dog2 = new Dog('小白',2);

2.继承

//定义一个Animal类
class Animal{
  name:string;
  age:number;
  constructor(name:string,age:number){
    this.name = name;
    this.age = age;
  }
  sayHello(){
    console.log('动物在叫');
  }
}

Dog Extends Anmial
此时,Animal被称为父类,Dog被称为子类
使用继承后,子类将会拥有父类所有的方法和属性
通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法,如果希望在子类中添加一些父类中没有的属性或方法直接加就行
如果在子类中添加了父类相同的方法,则子类方法会覆盖掉父类的方法,但是只在子类中覆盖,不会污染父类,这种子类覆盖掉父类方法的形式,我们称为重写

//Dog类继承Animal类
class Dog extends Animal{
  run(){
    console.log(`${this.name}在跑~`);
  }
  
  sayHello(){
      console.log('汪汪汪');
    }
}
const dog = new Dog('旺财',3);
dog.run();
dog.sayHello();
//Cat类继承Animal类
class Cat extends Animal{
  sayHello():void {
      console.log('喵喵喵');
  }
}
const cat = new Cat('咪咪',2);
cat.sayHello();

3.super关键字

class Anmial {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
    sayHello() {
      console.log('动物在叫');
    }
}

class Dog extends Anmial {
    age: number;
    constructor(name: string, age: number) {
      //如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
      //父类构造函数需要什么参数,在super中也必须传入对应类型的参数
      super(name);//调用父类的构造函数
      this.age = age;
    }
    sayHello(): void {
      //在类的方法中super就表示当前类的父类
      // super.sayHello(); 此处调用了父类Animal的sayHello方法
      console.log('汪汪汪');
    }
  }

4.抽象类

以abstract开头的类是抽象类,抽象类和其他类区别不大,只是不能用来创建对象,抽象类就是专门用来被继承的类,抽象类中可以添加抽象方法

abstract class Anmial {
    name: string;
    age: number = 347; //抽象类和接口不同,抽象类中的方法和属性可以有实际的值,抽象类和普通类完全相同,仅仅只是无法实例化
    constructor(name: string) {
      this.name = name;
    }
    /**
     * 定义一个抽象方法
     * 抽象方法使用abstract开头,没有方法体
     * 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
     */
    abstract sayHello(): void;
  }
class Dog extends Anmial {
    sayHello() {
      console.log('汪汪汪!!');
    }
  }
const dog = new Dog('旺财');
console.log(dog);
dog.sayHello();

5.接口

接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去使用,同名接口可以重复创建,相互之间是继承关系在创建类时需要满足所有创建的接口的要求(在写多个同名接口时,不可以对已经声明的类型进行更改)

interface myInterFace {
    name: string;
    age: number;
    // [propname:string]:any;
  };

  interface myInterFace {
    gengder: string;
  };


  const obj: myInterFace = {
    name: 'sss',
    age: 111,
    gengder: '男'
  };

接口也可以在定义类的时候去限制类的结构,接口中的所有的属性都不能有实际的值,接口只定义对象的结构,而不考虑实际值,在接口中所有的方法都是抽象方法,和抽象类的区别就是interface下的所有值不能有具体的值,在继承interface时使用的是implements而不是extends

interface myInter {
    name: string;
    sayHello(): void;
  }

定义类时,可以使类去实现一个接口,实现接口就是使类满足接口的要求

 class MyClass implements myInter {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
    sayHello() {
      console.log('大家好')
    }
  }

四、属性的封装

public 修饰的属性可以在任意位置访问(修改), 默认值,属性是在对象中设置的,属性可以被任意的修改,属性可以任意被修改将会导致对象中的数据变得非常不安全
private 私有属性,私有属性只能在类内部进行访问(修改),通过在类中添加方法使得私有属性可以被外部访问
protected 受保护的属性,只能在当前类和当前类的子类中使用(修改)

class Person{
    private _name:string;
    private _age:number;
    constructor(name:string,age:number){
      this._name = name;
      this._age = age;
    }
    //定义方法,用来获取name属性
    getName(){
      return this. _name;
    }
    //定义方法,用来设置name属性
    setName(value:string){
      this. _name = value;
    }
    getAge(){
      return this._age;
    }
    setAge(value:number){
      //判断年龄是否合法
      if(value>=0){
        this._age = value;
      }
    }
  };
  //使用自己定义的方法来控制
  console.log(per.getName());
  per.setName('koyoi')  ;
  console.log(per.getName());
  per.setAge(-18);//传入的年龄不符合判断条件,不生效
  console.log(per);
  per.setAge(18);
  console.log(per);

getter方法用来读取属性,setter方法用来设置属性,它们被称为属性的存取器
TS中设置getter方法和setter方法的方式如下

class Person{
    private _name:string;
    private _age:number;
    constructor(name:string,age:number){
      this._name = name;
      this._age = age;
    }
    get name(){
      return this._name;
    }
    set name(value:string){
      this._name = value;
      //this._age = 19; //可以设置非传入属性,不会报错,但不建议
    }
    get age(){
      return this._age
    }
    set age(value:number){
      if(value>=0){ //可加入判断条件
        this._age = value;
      }
    }
  };
const per = new Person('xili',17);
console.log(per.name); //通过getter获取_name
console.log(per.age); //通过getter获取_age
per.name = 'koyoi'; //通过setter设置_name
per.age = -18; //传入的年龄不符合判断条件,不生效
per.age = 18; //通过setter设置_age

使用private属性即使子类也无法访问到该属性,若想子类可以访问到属性,需要使用protected

class A{
    protected num:number;
    constructor(num:number){
      this.num = num;
    }
  };
  class B extends A{
    test(){
      console.log(this.num)
    }
  }

  const b = new B(123);
  console.log(b.num)

protected中一样可以使用get或set进行封装

class A{
  protected a:string;
  protected b:number;
  constructor(a:string,b:number){
    this.a = a;
    this.b = b
  }
}

class B extends A{
  get getA(){
    return this.a
  }
  get getB(){
    return this.b
  }
  set setB(value:number){
    this.b = value;
  }
}
let b = new B('xili',123);
console.log(b.getA);
b.setB = 321;
console.log(b.getB)

特殊语法

class C{
  //可以直接将属性定义在构造函数中
  constructor(public name:string,public age:number){ //此处必须带上public,protected或private关键字,否则无法进行赋值
  }
  //等价于
  // name:string;
  // age:number;
  // constructor(name:string,age:number){
  //   this.name = name;
  //   this.age = age;
  // }
}
const c = new C('xxx',111);
console.log(c)

五、泛型

在定义函数或是类时,如果遇到类型不明确就可以使用泛型

function fn<T>(a:T):T{
    return a;
   }

可以直接调用具有泛型的函数

let res = fn(10);  //不指定泛型,TS可以自动对类型进行推断
let res2 = fn<string>('hello')  //指定泛型(推荐)

泛型可以同时指定多个

function fn2<T,K>(a:T,b:K):T{
    console.log(b);
    return a;
}
fn2<number,string>(123,'hello')

箭头函数中使用泛型

let fn1 = <T>(a:T):T=>{
  return a
}
let res1 = fn1<number>(233);
console.log(res1)

T extends Inter 表示泛型T必须是Inter实现类(子类)
在下面的例子中,参数a只需要满足泛型T即可,而泛型T继承自Inter,上下关系即为a必须符合泛型T,T必须继承Inter,而a不是必须完全符合Inter(只需要符合Inter包含的属性就行)(意即a不用和接口Inter规定的格式一致,只需要满足继承了接口Inter的泛型T即可)

interface Inter{
    length:number;
}
function fn3<T extends Inter>(a:T):number{
    return a.length
   }
fn3('123');//此处传入string类型,因为string带length属性,而Inter只设置了length属性,所以传入参数合法,且此处参数只用满足泛型T,所以即使格式与接口Inter不完全相同也没有问题

在类中使用

   class MyClass<T,K>{
    name:T;
    age:K;
    constructor(name:T,age:K){
      this.name = name;
      this.age = age
    }
   }
   const mc = new MyClass<string,number>('xili',17);
   console.log(mc);