TypeScript

TypeScript解决JavaScript类型系统的问题TS大大提高代码的可靠程度。JavaScript自有类型系统的问题,所以我们可以使用TypeScript来健壮我们的代码。首先我们需要了解强类型弱类型语言的区别。

  • 强类型:语言层面限制函数的实参类型和形参类型类型必须一致,可以说强类型不允许随意的隐式转换。

  • 弱类型:若类型语言不会限制实参的类型,允许隐式类型的转换

前置知识

静态类型与动态类型

  • 静态类型:一个变量声明时它的类型就是明确的,声明过后,他的类型就不允许再修改。

  • 动态类型:在运行阶段才能明确变量类型,变量的类型随时可以改变。也可以说变量是没有类型的,变量中存放的值是有类型的。

JavaScript类型系统特征

JavaScript是弱类型且动态语言,JavaScript早期需求很简答;脚本语言, 没有编译环节。

而强类型的优势在于:

1、错误更早暴露

2、代码更智能,编码更准确

3、重构更牢靠

4、减少不必要的类型判断

TypeScript概述

TypeScriptJavaScript的超集,同时TypeScript功能更强大,生态也健全更完善。说了那么多优点,它的缺点是语言本身多了许多概念(接口、泛型等),在项目初期,TS会增加一些成本。

准备工作(安装与编译)

全局安装

    npm install -g typescript //mac下安装前面需要加sudo。也可以在项目中局部安装

编译

  • 可以执行下面命令进行编译,会在当前目录下产生一个test.js文件
    tsc test.ts

这样可能还是比较麻烦,我们可以借助插件方便我们提高效率:

    npm install -g ts-node

安装完后只需执行ts-node test.ts就可以在控制台查看输出结果。

tsconfig.json配置

生成配置文件

  • 我们可以通过tsc --init去生成ts配置文件:
   tsc --init

编译文件

  • 会生成一个tsconfig.json文件,我们在ts文件中可以随便写点什么:
    //测试tsconfig.json
    const test:string='tsconfig.json';
  • 然后打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true(这个配置是编译后不输出注释),取消掉注释,然后执行命令:
    tsc
  • 这时候打开生成的js文件,发现没有注释,说明成功。

tsconfig.json配置项

下面给列一下常用的配置项,更多可以查看 配置查询网站

include 、exclude 和 files

  • 如果有多个ts文件,只想编译一个可以在ts配置项中加入include
    "include":["text.ts"],
  • 如果想除了某个文件不编译,剩下的都编译,可以使用exclude
     "exclude":["text.ts"],
  • filesinclude没有什么区别:
     "files":["text.ts"],

removeComments

  • 这个配置意思就是编译后不输出注释

strict

  • 这个设置为true,就代表严格执行ts语法,要严格按照ts语法来编写。

noImplicitAny

  • 允许你的注解类型 any 不用特意表明,如果此时设置了true,看例子:
    //这时候编译会报错
    function test(name) {
         return name;
    }
   
    //正确
     function test(name:any) {
         return name;
    }

strictNullChecks

  • 意思就是,不强制检查null类型,此时如果配置为true看例子:
    //此时就不会报错
    const test: string = null;

outDir和rootDir

  • 此项配置是来指定文件目录和打包后存放目录,rootDir为文件目录,outDir为打包后保存的目录
    {
        "outDir": "./build" ,
        "rootDir": "./src" ,
    }

编译ES6语法

  • 可以使用targetallowJs,target默认为true
    "target":'es5' ,  // 可以转化为目标标准
    "allowJs":true,   // 这个配置项的意思是联通

sourceMap

  • sourceMap简单说,Source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。调试的时候就可以使用sourceMap文件来调试ts源代码。

noUnusedLocals

  • 设置noUnusedLocals为true,编译代码:
    const name:tring='111';
    export const age = "text";
  • 这时候就会报错,因为有name变量没有使用。

原始数据类型

let a:string='lonjin';

let b:number=100; //NaN Infinity

let c:boolean=true;

let g:void=undefined; //空值,常用于表述没有任何返回值的函数

let h:null=null;

let i:undefined=undefined;

在非严格模式(strictNullChecks)下,string, number, boolean 都可以为null

let d:string=null;
let e:number=null;
let f:boolean=null;

如果想使用Symbol,必须在tsconfig.json中修改lib包含ES2015:

let j:symbol=Symbol();

如果想使用console.log,也需要在tsconfig.jsonlib中添加dom选项

//tsconfig.json
"lib": ["es2015","dom"],

//index.ts
console.log(111)

TypeScript作用域问题

ts中,默认文件中的成员会作为全局成员,所以多个文件中有相同成员就会出现冲突(尤其在小程序项目中使用ts时),解决方法如下:

  • 方法1:创建一个立即执行函数,隔离作用域
(function () {
  const a = 123
})()
  • 方法2:在当前文件使用 export,也就是把当前文件变成一个模块。在小程序中,每个page下的index.ts中如果不写export就会报作用域错误。
const a = 123
export {}

TypeScript中Object类型

ts中的Object类型并不单指对象类型,而是泛指非原始类型,也就是对象、数组、还有函数。

// object 类型是指除了原始类型以外的其它类型,所以可以为函数,数组这种值
let obj:object=function(){};
let obj2:object=[1,2,3];

如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」

let obj3:{name:string,age:number}={name:'lonjin',age:18}

TypeScript中数组类型

数组类型的两种表示方式:

//方式1
let arr:Array<number>=[1,2,3,4,5];
//方式2
let arr2:number[]=[1,2,3,4,5];

如果数组中又有number类型又有string类型,则可以用符号来区别定义

let arr:(number|string)[]=['tom',1];

项目中经常遇到数组中有对象的存在,可以使用下面方法去定义:

let arr:{name:string,age:number}[]=[
    {name:'tom',age:18}
]

这样写起来会很麻烦,我们可以使用ts中的类型别名来解决这个问题(后面会详细讲解)

type PeopleType={name:String,age:Number};

let arr:PeopleType[]=[
    {name:'lonjin',age:18}
]

元组类型

在数组中如果里面又有stringnumber,可以使用来进行定义,但一定程度上并不严格。比如改成下面这种格式:

    let arr:(number|string)[]=[111,'222',111];

ts并没有报错,如果想要严格限制,则可以使用元组来进行约束,元组就是明确元素数量和类型的一个数组

    let arr:[number,string,number]=[111,'222',111];

Enum枚举

平时我们可能需要定义多种状态来表示对应的含义,大多数情况下我们会这么写:

const Status={
    ONE:0,
    TWO:1,
    THREE:2
}

function getName(status:any){
    if(status===Status.ONE){
        return 'one'
    }else if(status===Status.TWO){
        return 'two'
    }else{
        return 'three'
    }
}

这时候枚举就派上用场了:

//默认为数字,从0开始
enum Status{
    one,
    two,
    three,
}

console.log(Status.one,Status.two,Status.three)
//0 1 2

//也可以为字符串

const enum PropStatus{
    one='a',
    two='b',
    three='c'
}

console.log(PropStatus.one,PropStatus.two,PropStatus.three)
//a b c

细心的童鞋可以看到我在后面的枚举前面加了const,我们编译一下看看有什么不同:

var Status;
(function (Status) {
    Status[Status["one"] = 0] = "one";
    Status[Status["two"] = 1] = "two";
    Status[Status["three"] = 2] = "three";
})(Status || (Status = {}));
console.log(Status.one, Status.two, Status.three);

console.log("a" /* one */, "b" /* two */, "c" /* three */);
  • 可以看到,第一个没加const,它编译后会入侵编译结果,而如果使用常量枚举,就不会入侵编译结果。

TypeScript函数类型

函数类型比较简单,主要就是参数的约定以及返回值约定:

function fn(num:number,num2:number,...args:number[]):string{
    return 'fn'
};
fn(1,2)
fn(1,2,3)
fn(1,2,3,4)

箭头函数:

const fn4=(a:number,b:number):string=>{
    return 'lonjin'
}

可选参数和默认参数:

function fn2(a:number,b?:number,c:number=4,...agrs:number[]){
    console.log(a,b,c,agrs)
}

fn2(1,2)//1 2 4 []
fn2(1,2,3)//1 2 3 []
fn2(1,2,3,4,5)//1 2 3 [ 4, 5 ]

TypeScript任意类型

很多场景下我们并不需要对类型有明确的约束,这时候就可以使用anyany虽好,可不要过度使用哦!

function fn(val:any){
    console.log(val)
}
//可以传任意类型
fn(1)
fn('lonjin')
fn(true)

TypeScript隐式类型推断

隐式类型推断很好理解,如果我们在声明一个变量时候没有定义它的类型,ts根据变量后的值进行类型推断:

let n=100;
//相当于
let n:number=100;

如果我们在声明时候没有赋值,则会被推断为any

let n;
// 相当于 let n:any;可以赋值任意类型数据
n=100;
n='lonjin';
n=true;

TypeScript类型断言

在实际场景中,我们可能会遇到以下情况:从接口中获取到一个数组,数组中存放的都是都是数字,我们需要取到某个值然后再进行一系列的计算:

//接口中返回的数组
let arr=[1,2,3,4,5];

let num=arr.find(n=>n>4);

//报错:ts并不确定num是不是一个number let num: number | undefined
let num2=num+1

这时候我们就可以使用类型断言进行处理,告诉ts这确定是一个数字

let arr=[1,2,3,4,5];

let num =arr.find(n=>n>4);

let num2=num as number

let num3=num2+1

断言除了as,还有另外一种书写方法:

let arr=[1,2,3,4,5];

let num =arr.find(n=>n>4);

let num3=<number>num+1

TypeScript接口

接口主要约束对象中的数据类型,基本使用如下:

interface People{
    name:string,
    age:number,
}

const people:People={
    name:'lonjin',
    age:18
}

很多情况下,我们可能确定不了是否会有某个值,这时候我们就需要使用可选参数了:

interface People{
    name:string,
    age:number,
    sex?:boolean,
}

const people:People={
    name:'lonjin',
    age:18,
}

有时候我们根本不知道这个对象中要放什么,可以理解为动态场景,可以给对象任意添加一些数值:

interface People{
    name:string,
    [prop:string]:any
};

const people:People={
    name:'lonjin',
};

people.say=function(){
    console.log('hello')
};

people.home='beijing'

如果我们希望某些值设置为只读,可以在接口中加入修饰符readonly:

interface People{
    name:string,
    age:number,
    readonly hasCar:boolean,
}

const people:People={
    name:'lonjin',
    age:18,
    hasCar:false
}
//报错:无法分配到 "hasCar" ,因为它是只读属性。
people.hasCar=true;

TypeScript类的基本使用

基本使用比较简单,我们直接看代码:

class People{
class People{
    name:string;
    age:number;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    say(message:string):void{
        console.log(`my name is ${this.name},${message}`)
    }
}

let tom=new People('tom',18)

类的修饰符

在类中,一共有三个修饰符:

  • pubilc:公有成员,如果没有加修饰符,则默认为pubilc

  • private:只能在成员方法内部访问

  • protected:也不能在外部访问,和private的区别就是,protected是只允许在子类中访问成员

class People{
    public name:string;
    private age:number;
    protected sex:boolean;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
        this.sex=true;
    }
    say(message:string):void{
        console.log(`my name is ${this.name},${message}`)
    }
}

class Son extends People{
    constructor(name:string,age:number){
        super(name,age);
        console.log(this.sex)//true
    }
}

除了上面提到的,还可以设置只读属性readonly。顾名思义,只允许读取,不允许修改。如果前面有修饰符,就跟在修饰符后面:

    public name:string;
    private age:number;
    protected readonly sex:boolean;//只读属性
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
        this.sex=true;
    }
    say(message:string):void{
        console.log(`my name is ${this.name},${message}`)
    }
}

类与接口

  • 如果类与类之间有共同特性,可以使用来进行抽离,举个例子,人会吃东西,也会跑,车只会跑,但不会吃东西,所以我们可以把这些抽离出来:
interface Eat{
    eat(food:string):void;
}

interface Run{
    run(type:string):void;
}

class People implements Eat,Run{
    eat(food:string):void{
        console.log(`eat ${food}`)
    };
    run(type:string){
        console.log(`people ${type}`)
    }
}

class Car implements Run{
    run(type:string):void{
        console.log(`car is ${type}`)
    }
}

抽象类

抽象类也用来约束子类当中某些成员,与接口不同的是,抽象类可以包含具体的实现,也可以只约束方法,具体例子如下:

//抽象类
abstract class Eat{
    eat(food:string):void{
        console.log(`eat ${food}`)
    }
    //约束People中必须有run方法
    abstract run(type:number):void;
}

class People extends Eat{
    run(type: number): void {
       console.log(`run ${type}`)
    }
}

let tom=new People();
tom.eat('foods');
tom.run(100)

泛型

泛型就是在定义函数、接口、或者类的时候没有具体定义类型,在使用时候才进行定义:

//比如我们写一个函数,函数中接收两个值,最终return一个数组回去,我们接收的参数可能是字符串,也可能是数字

function add<T>(n:T,m:T):T[]{
    let a=[n,m];
    return a;
}

let str=add<number>(1,2)

let num=add<string>('one','two')