我的TypeScript随笔记

114 阅读15分钟

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

一、TypeScript简介

  • TypeScript是一种由微软开发的一门编程语言,它实际上是js语言的一种扩展(JS的超集),扩展了更多大语言(面向对象语言)所特有的语法。

  • TypeScript支持所有JavaScript语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作。TypeScript是为 大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性。

  • 可以在任何浏览器,任何计算机和任何操作系统上运行,并且是开源的。本课程介绍了TypeScript的基本概念、常用语法和高级特性。

  • 官方网站:www.typescriptlang.org

image-20210906144213429

二、安装和编译

安装TypeScript(目的是使用全局命令tsc,把ts文件编译成js文件)

yarn global add typescript

编译(compile)

tsc 文件名.ts

三、类型注解

即对变量或者函数的类型进行解释,就是类型注解,其目的是约束变量或者函数的结构。

基本类型

TypeScript中的基本类型都可以在这里用作类型约束,语法为let | const | var + 变量名:变量类型 = 值 在这里的类型必须符合变量类型所指定的类型 ,否则会报错。

/* 
var | let | const + 变量名:变量类型 = 值

变量类型:
    string/number/boolean/null/undefined/symbol/object
    
TypeScript中额外的变量类型:
    1. void === null | undefined
    2. any  表示任意类型
*/
let name:string = "张三";
name = 123;//报错

引用类型

  1. 只要求是引用类型即可。如果只是要求变量的类型是个引用类型,具体是数组还是json对象或者是函数,我们并不关心,则可以使用js中的object来进行类型约束,但是这种方式用的很少,不太使用。

    let o:object = [];
    o = {};
    o = "";  //报错
    
  2. 必须是数组

    let arr:string[] = ["a","b","c"];
    let arr1:number[] = [1,2,3];
    
  3. 必须是函数

    let func:Function = ()=>{}; //这种方式不能精确的约束函数参数的类型和返回值的类型
    let func:(a:number,b:number)=>number = (a,b)=>a+b; //这种方式则约束了函数接受的两个参数a和b以及返回值的类型都必须为number类型
    
  4. 必须是json对象

    let json:{name:string,sex?:string} =  {name:"张三"};
    
    //如果json对象的某个属性为函数怎么处理 (?表示该属性可选)
    let json1:{name:string,sex?:string,func:(a:number,b:number)=>number} = {
        name:"张三",
        func:(a,b)=>a + b,
    }
    json1.func(1,2); //在调用函数的时候也必须传递两个类型为number类型的,否则会报错
    
    //如果这个json对象有任意多的属性怎么办
    let json2:{a:number,b:string,[propName:string]:string | number} = {
        a:1,
        b:"s",
        aaa:"",
        //..其他属性可以任意写,只要类型为number或者string都可以
    }
    

四、类型别名

类型别名的作用是:⾃定义类型(起作用是约束变量或者函数的结构),如果有很多个变量,他们的结构都是相同的,则我们可以把约束这些变量结构的类型起一个别名,然后方便我们使用。

//类型别名   type + 类型别名(首字母大写)= 类型约束条件
type Obj = {name?:string,sex:string,age:number,cls:string,func?:(a:number,b:number)=>number};
var myObj:Obj = {
    // name:"",
    sex:"男",
    age:10,
    cls:"06",
}
var myObj2:Obj= {
    name:"张三",
    sex:"男",
    age:20,
    cls:"09",
}
var myObj3:Obj = {
    name:"李四",
    sex:"男",
    age:20,
    cls:"09",
}

我们可以在一个单独的文件中集中管理类型别名,然后导出接口供其他文件使用

//export导出类型别名
export type Obj = {name?:string,sex:string,age:number,cls:string,func?:(a:number,b:number)=>number};

//使用
import {Obj} from 路径

注意:类型别名首字母大写

五、接⼝(Interface)

接口的定义和使用

接⼝仅约束变量或者函数的结构,不要求实现,使⽤更简单。

// Person接⼝定义了结构(即将来凡是使用该接口约束的对象,只能写成这样)
interface Obj{
    name?:string,
    sex:string,
    age:number,
    cls:string,
    func?:(a:number,b:number)=>number
};
let O:Obj = {
    sex:"男",
    age:20,
    cls:"07",
    func:(a,b)=>a + b,
}
​

注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,如下代码就会报错👇。

```
interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}
//number不是string的子集,会报错
```

我们可以在一个单独的文件中集中管理接口,然后导出接口供其他文件使用。

// export 导出接口
export interface Person {
    name?:string,
    sex:string,
    age:number,
    cls:string,
    func?:(a:number,b:number)=>number
 }

注意:接口首字母大写

接口的继承

  1. 接口继承接口
interface A {
    name:string,
    sex:string
}
interface B extends A{
    age:number
}
let a:B = {
    name:"张三",
    sex:"男",
    age:20
}
  1. 接口继承类(常见的面向对象语言中,接口是不能继承类的)

接口继承类其实就是接口继承接口,因为ts中在创建一个类的时候,同时也会创建一个对应的接口,这个接口是对当前类的实例对象结构的一个描述(包括实例属性、方法以及原型方法),例如:

//现在创建一个Sum类
class Sum{
   a:number;
   b:number;
   constructor(a:number,b:number){
      this.a = a;
      this.b = b;
   }
    getTotal(){
      return this.a + this.b;
   }
}
//同时ts在编译的时候也会在内存中创建一个对应的接口,来描述该类的实例对象的结构,相当于向下面一样创建一个接口
interface Sum{//这个接口看不到,但是确存在着
   a:number,
   b:number,
   getTotal():number,
}

//所以下面我另外写一个接口,它可以直接继承Sum类(要记住:其实继承的是Sum类对应的接口)
interface OtherInterface extends Sum{
   name:string,
}

类型别名和接口的区别

接口能够继承、但是类型别名不可以。

六、函数

声明式函数约束

  1. 直接在函数内部,对函数参数和返回值进行类型约束,可选参数在后面加上?即可。
function threeSum(a:number,b:number=20,c?:number):number{
    return a + b + c;
}
threeSum(1,2,3)
  • 注意:可选参数只能写在参数列表的最后,并且一个已经设置为可选的参数,不能同时又设置默认值。

函数表达式约束

let threeSum = function (a:number, b:number, c:number):number {
   return a + b + c;
}

以上代码编译是能都通过的,但是,上面的写法只是对等号右边的匿名函数做了类型约束,等号左边的threeSum的类型是ts自己类型推断出来的,如果我们想直接对threeSum做类型约束,需要像下面这样写👇。

let threeSum: (a: number, b: number, c: number) => number = function (a, b, c) {
   return a + b + c;
}
//(a: number, b: number, c: number) => number这段代码,不是函数的具体实现,只是一个类型而已,对threeSum这个//变量起到类型约束的作用

利用类型别名对函数进行类型约束

//该类型别名ThreeSum表示即将被约束的函数应该长这样:接受两个参数,他们的类型都是number,并且返回值也是number类型的
type ThreeSum =(a: number, b: number, c: number) =>number;
const threeSum:ThreeSum = function (a, b, c) {
   return a + b + c;
}

利用接口对函数进行类型约束

//该类接口ThreeSum表示即将被约束的函数应该长这样:接受两个参数,他们的类型都是number,并且返回值也是number类型的
interface ThreeSum{
   (a:number,b:number,c:number):number,//如果没有函数名字,则表示被该接口约束的变量本身就是一个函数,而不是一个对象,对象下面的某个属性是函数
}
const threeSum:ThreeSum = function (a, b, c) {
   return a + b + c;
}
​

函数重载(了解)

当你需要用同一个函数,但是,该函数接受的参数个数,或者参数类型,或返回值类型不同时,就可以利用函数重载来实现该函数的类型约束。重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

比如现在我要写这么一个函数,接受两个参数a,b,要求a,b必须同时为string或者number。返回值类型必须和参数类型一致

function towSum(a,b){
    return a + b;
}
//可能想到的解决方案--利用泛型
function<T>(a:T,b:T):T{
    return a + b;
}
//但是这样ts会报错,因为这里T可以是任意的类型,如果传递的是一个对象,则对他们使用加法,则是不合适的
​
​
//正解:利用函数重载
function towSum(a: number, b: number): number;
function towSum(a: string, b: string): string;
function towSum(a, b) {
    return a + b;
}
/*
上例中前两次都是对函数类型(参数和返回值的)的约束,最后一次是函数实现
​
*/
  • 注意:为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
  • 注意,function sum(a:any, b:any): string | number并不是重载列表的一部分,因此这里只有两个重载:一个是接收数字,另一个接收字符串。 以其它参数调用 sum会产生错误。另外定义函数时的返回值类型必须包含函数重载时的返回值,否则也会报错。

七、类型之间的运算

就像js变量之间可以运算一样,在ts中类型之间(包括内置类型以及类型别名和接口之间)也是可以运算的,只是运算的方式没有那么多。

或运算(|)

或运算后产生的类型我们称之为联合类型

//a的类型可能是number也可能是string,则可以做如下处理
let a:number | string = "字符串";
a = 123;
//变量o的结构可能是Alias1或者是Alias2或者是I
type Alias1 = {name:string}
type Alias2 = {sex:string}
interface I{
    age:number
}
type Alias3 = Alias1 | Alias2 | I;
let o:Alias3 = {
    name:"张三",
    sex:"男",
    age:20,
}

与运算(&)

与运算后产生的类型我们称之为交叉类型,注意交叉类型只能用于引用类型,基本类型不能使用。

例如:一个变量不可能即是string又是number类型的👇。

type Obj1 = {name:string};
type Obj2 = {age:number}
type Obj = Obj1 & Obj2;
const myObj:Obj = {//这是myObj的类型被Obj1和Obj2同时约束 ,所以必须同时具有name和age两个属性
    name:"张三",
    age:20
}

八、类的约束

  • ts中的类和es6中⼤体相同,这⾥重点关注ts带来的访问控制等特性
class Parent {
     static staticProp = "somevalue";//静态属性
     sex:string;
     private _foo:string = "foo"; // 私有属性,不能在类的外部访问
     protected bar = "bar"; // 保护属性,可以在⼦类中访问
     // 参数属性:构造函数参数加修饰符,能够定义为成员属性
     constructor(public name = "张三") {
     
     }
     // ⽅法也有修饰符
     private someMethod(a:string,b:string):void {}
     // 存取器:属性⽅式访问,可添加额外逻辑,控制读写性
     get foo() {
        return this._foo;
     }
     set foo(val) {
        this._foo = val;
     }
 }

通过抽象类(Abstract Class)来约束子类

抽象类是供其他类继承的基类,抽象类不允许被实例化,抽象类中的抽象方法和抽象属性必须在子类中被实现

如何定义抽象类?---在class前面加上关键字abstract 即可。

abstract class Sum {
  abstract c:number;//这是一个抽象属性,必须被其子类实现
  a:number;
  b:number;
   constructor(a:number, b: number) {
      this.a = a;
      this.b = b;
   }
  abstract getTotal():number;//这是一个抽象方法,必须被子类实现
​
}
class MySum extends Sum {
   c = 100;//实现抽象类中的抽象实例
   a:number;
   b:number;
   constructor(a: number, b: number) {
      super(a,b);
   }
   getTotal(){//实现抽象类中的抽象方法
      return this.a + this.b;
   }
}
const sum = new MySum(1, 2);
sum.getTotal();

通过接口来约束类(类实现--implements接口)

假设现在有一个需求:有n个类,他们的实例对象都需要有一个sayName方法和name属性,如何用ts去约束他们的类型呢?

这时我们可以通过interface定义接口,然后用过implements(翻译过来是实现的意思)关键字去实现对类结构的约束(注意只能去约束类的实例对象上的属性和方法),代码如下👇。

interface Test{
    sayName():void,
    name:string,
}
interface Test2{
    sayName2():void,
    name2:string,
}
​
class Hello implements Test{//Hello类必须实现Test接口所规定的方法和属性(注意是实例属性和实例方法)
    name = "张三"
    sayName(){}
}
//一个类可以同时实现过个接口
class Hello2 implements Test,Test2{
//Hello2类必须同时实现Test接口和Test2接口所规定的方法和属性(注意是实例属性和实例方法)
    name = "张三";
    name2 = "李四";
    sayName(){},
    sayName2(){}
}
​

九、泛型

泛型(Generics)是指在定义函数、接⼝或类的时候,不预先指定具体的类型,⽽在使⽤的时候再指定。

类型的⼀种特性。以此增加代码通用性。类似于函数的形参,这里其实指的是类型参数(或者是类型变量)。

函数中使用泛型

// 假设我们现在有一个需求:函数参数的类型必须并不确定,需要在执行的时候确定,现在我们就可以用泛型
function test<T>(a:T,b:T){
    //...你的代码
        
}
test<number>(1,2);//这时类型变量T指的是number
test<string>('张','三');//这时类型变量T指的是string

接口或者类型别名中使用泛型

interface Test<T,S>{//两个类型参数T和S(形参)
   foo:T,
   bar:S
   log():T,
}
const obj:Test<string,number> = {//传递类型参数 string和number (实参)
   foo:"foo",
   bar:123,
   log(){
      return 'foo'
   }
}
​
//类型别名也可以使用泛型
type Test<T,S> = {
   foo:T,
   bar:S
   log():T,
}
const obj:Test<string,number> = {
   foo:"foo",
   bar:123,
   log(){
      return 'foo'
   }
}
​

在类中使用泛型

class Hello<T,S>{//两个类型参数T和S,在Hello类的内部可以用T和S对实例属性和方法进行类型约束
​
   foo:T;
   bar:S;
   constructor(foo:T,bar:S){
      this.foo = foo;
      this.bar = bar;
   }
   //类中sum方法的重载
   sum(a:string,b:string):string;
   sum(a:number,b:number):number;
   sum(a:any,b:any){
      return a + b;
   }
   handleFoo():T{//用T约束方法handleFoo的返回值类型
      console.log('this.foo',this.foo);
      return this.foo;
   } 
}
const hello = new Hello<string,any[]>("foo",[]);//可以显示的指定类型实参string和any[]
const hello1 = new Hello("foo",[]);//也可以不用显示的指定类型实参,ts会自己进行类型推断,从而判断出T和S的类型

十、类型断言(了解)

类型断言的意思就是,我们认为的告诉ts,该标识符是个什么类型,而不是有ts自己去判断。通过as关键字可以实现类型断言。

//在ts中直接这样为window的某个属性赋值会报错,提示Window类型中不存在foo属性
window.foo = "foo";
//如何解决---断言
(window as any).foo = "foo"
//这里的意思是我们把window的类型断言为any,而一个any类型的变量,可以访问改变量下面的任何属性

十一、TypeScript配置文件tsconfig.json(了解)

改文件一般放在项目的根目录。

{
        "compilerOptions":{//编译ts时的配置选项
        "outDir":"./dist",//目标输出路径
        "noEmitOnError":true, // 编译的源文件中存在错误的时候不再输出编译结果文件,默认为false
        "noImplicitAny":true,//控制当源文件中存在隐式的any的时候是否报错,默认false,不会报错
        "noImplicitThis": true, // 当源文件中存在this为any的时候报错,默认为false不会报错
        "target": "es5",//生成的js符合什么版本的js规范,其默认值为es3
        "removeComments": true, // 是否在输出文件中清除源文件中的注释,默认false不删除
        "lib":["ES2015","DOM","DOM.Iterable"],//这个是用于指定要引入的库文件,属性值为一个数组,有es5、es6、es7、dom四个值可选,如果不配置lib,那么其默认会引入dom库,但是如果配置了lib,那么就只会引入指定的库了,
        "module":"CommonJS",//这个用于指定要使用的模块标准,如果不显式配置module,那么其值与target的配置有关,其默认值为target === "es3" or "es5" ?"commonjs" : "es6",所以当target为es3或者es5的时候,module的默认值为commonjs,当target为其他的值的时候,那么module的默认值为es6
        "moduleResolution":"Node",//配置模块解析规则
        "typeRoots":[//这个用于指定类型声明文件的查找路径。默认值为node_modules/@types,即在node_modules下的@types里面查找,需要注意的是这里仅仅是d.ts文件的查找路径
            "node_modules/@types",//默认值
        ],
        "jsx": "react", // 在 .tsx文件里支持JSX
    },
        "files": ["index.ts"],//由于默认情况下,tsc会编译当前项目下的所有ts文件,所以如果我们可以通过files配置来指定编译的入口文件,files的属性值为一个数组,可以指定要编译的具体文件,但是其不能使用通配符进行指定
        
        "includes":["src","index.ts","module1.ts"],//正是由于files配置无法使用通配符进行配置,所以添加了includes配置,includes配置属性值也是一个数组,但是includes中可以使用通配符,并且可以和files一起使用,最终编译的源文件包含,files和includes的合集
    
        "exclude": ["index.ts"],//我们可以通过excludes配置来排除掉includes配置中包含的源文件,需要特别注意的是,excludes只对includes中包含文件起到排除的作用,其无法排除files中配置的源文件
}

十二、声明文件(了解)

使⽤ts开发时如果要使⽤第三⽅js库的同时还想利⽤ts诸如类型检查等特性就需要声明⽂件,类

似 xx.d.ts。

如果不存在该js文件对应的类型声明文件,则需要我们单独下载。

yarn add @types/xxx
  • 当然有些模块可能没有现成的类型文件,也就是说当前模块可能不支持ts,这时候直接用js即可,因为ts包含js,是js的超集。

互相学习,欢迎批评和建议!!!