TS学习

160 阅读13分钟
  • 个人理解JS自身没有类型系统,导致js可以非常的随意的声明变量(let var const,仅仅三个关键字,就可以声明任意类型的变量,还可以随时变换变量的数据类型,可以说是非常炸裂,这既是JS的优点,也是他的缺点),有时就会显得非常的混乱,因此,TS不仅规范了JS变量的声明,并且引入补全了更多关键字(如interface等),使JS编程从过去单一简单的函数式编程方式,具有了面向对象编程的能力,使JS成为一个更加全面的语言,有利于后期使用NodeJs书写相应的服务端应用,也有助于程序员使用Vue,React等前端框架编写的前端项目代码变得更加成熟和更加有可维护性。
  • TS最终的目的就是提前预判并约定变量和函数,以及函数返回值的类型,然后在程序中遵守这个约定,非常适合在前端程序中的公共方法公共插件ajax等文件中使用,不仅保证了输出数据的类型稳定性,提前预防输入参数的数据类型错误,还会在后期代码维护的过程中排除掉因为数据被重新赋值导致类型发生变化而导致的bug(这类bug通常在js中很难被发现)。

1. 基础类型

给基础类型变量规定类型, 这些都是官方的一些类型,可以直接用,也是通俗易懂的,可以自行查看官网。一般情况下不建议使用any, 因为这样就失去了类型校验的意义了。

let isBool: boolean = true;
let str: string = 'abc';
let num: number = 123;

let arr1:number[] = [1,2,3];
let arr2: Array<string> = ['1','2','3'];

let tuple:[number,string] = [1,'2'];
tuple[3] = '3'; // 越界元组的数据设置类型使 number | string

enum Sum {one =1 ,two, three};
let n:Sum = Sum.two; // 2
let s:string = Sum[3]; // 'three'

// any 任意类型,用于不太确定的类型(一般情况下不建议使用)
let arrAny: any[] = [1,'2',true];

// void 非任何类型,常用于function 无返回值的情况
function func():void {};

1.1 any

表示可以是任何类型,Js目前所有的变量,函数等等,可以看作是默认为any,但是在Ts中这属于无效约束,没有约束效果,尽量避免使用。

1.2 void

any 相反,表示没有任何类型,一般想着重强调一个函数没有任何返回值,无需关注这个函数的返回值(比如异步函数的返回值)。如果把这个类型赋值给一个变量,那么这个变量只能是undefined(未定义)。

const a:void = undefined;
const func = ():void => {};

1.3 never

空类型,底部类型,这个类型的变量无法被赋值,取交集为自身never,取并集无视。这个类型的作用更多是用来进行类型运算的,和表示一些抛出异常的类型。这个后面在类型计算的时候还会提到。

type Exclude<T, U> = T extends U ? never : T; // 接收两个泛型,如果T继承于U则返回never,否则返回T

1.4 enum

enum类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字。

enum Color {one = 1,two,three} // 默认从零开始叠加,也可以自定义开头叠加
const c: Color = 3; // c 只能是枚举范围内的数据(1,2,3 或者 Color.two),否则报错。
const cName: string = Color[2] // 'two', 也可以根据数值,推算出字面量。

1.5 unknown

不可预先定义的内容,就是事先谁也不知道是啥类型的变量,这个变量在运行时可以被赋值为任何类型,但是在运行之前的静态检查中无法调用任何类型的原型方法。我觉得主要是为了代替any

const str1:unknown = 'string';
str1.length; // 报错 
const str2:any = 'string';
str2.length; // 不报错
// (str1 as any).length 相当于 (str1 as unknown as string).length
// 使用场景
function func(arg:unknow):number {
    if(Array.isArray(arg)) {
        return arg.length; // 静态检查认为这个是个数组,可以返回数组的原型上方法
    }
    return arg.split(','); //  静态检查认为这个是个unknown,直接使用方法汇报错。
    // 用 (arg as string).split(',') 断言一下即可通过静态检查。 
}

1.6 类型断言

比如某些时候,我们需要用到某个数据类型原型链上的方法,但是我们无法确定传过来的值的具体类型时要用到的方法

let strObj: any; // strObj 可以是字符串可以是数组也可以是带length属性的对象
let strNum1:number = (<string>strObj).length; 
let strNum2:number = (strObj as string).length; // 两种方式都是可以的。

1.7 object,null,undefined

  1. nullundefined类型的数据只能赋值 null和 undefined,只能代表他自己,没啥大用。
  2. object意思是表示非原始类型,而ts中numberstringbooleansymbolnullundefined等是原始类型,这些数据不能赋值给object类型的数据。
  3. Object,{}:Object 表示具有 toString, hasOwnProperty 等原型链上的 prototype 上面的方法的 JavaScript 对象,所有的值(包含原始类型和非原始类型)都可以赋值给 Object 类型的变量,{}类型同上。
const nu:null = null;
const nd:undefined = undefined;
const obj:object = {} || {a:1} || [] || [1,2,3] // 除此之外还可以赋 Set,Map,Function等非原始类型。

基础类型是非常多且琐碎,他们并不是TS的重点,大致做到了解即可,后续有需要随用随查即可。重点在使用泛型,进行相关的类型推导。

2. 类型别名 接口 类

2.1 type(类型别名)

顾名思义,就是把能用到的类型,按照自己的意愿组合起来后设置一个别名,方便后面直接使用,(类似与Js中命名变量的用法)

type D = number; // 基础类型
type A = string|number; // |:或,要么是string要么是number(联合类型),&:且,既是string又是number === never(交叉类型)。 
type B = {name:string}; //非原始类型 其实可以把这个看做成 {name:string}作为一个interface 赋值给 type B
type BC = B & {grade:number}; // === {name:string, grade:number}; //相当于 BC 继承 B 且有自有属性grade (交叉类型)

2.2 interface(接口)

  1. 为对象内部按照自己的意愿设定一些类型
  2. 也可以设定一些方法,被一些类实现。
  3. 也可以描述一些函数
  4. 接口可以继承接口。也可以继承类型别名
interface func {
    (str:string,arr:string[]):boolean // 表示这个接口描述的是一个函数的参数和返回的类型
} // 这个和用 interface 描述一个数组的类型约束很类似。
const func1:func = (str, arr) => arr.includes(str);
func1('123123', ['12321','aedada']);
interface obj {
    a:string,
    b:number
} // 这样就写死了,必须是只有a和b两个属性,如果想写一个比较灵活的对象 得用索引签名
interface objC extend obj {
    c:string[]
}
interface api {
    addFun(param:{}):{errorMsg:string,status:number,data:any[]}
    deleteFun(param:{}):{errorMsg:string,status:number,data:any[]}
} // 可以把这个当成描述一个具有两个函数属性的对象,也可以使用class类来implement 接口中的方法。
const apiFun:api = {
    addFun:(param) => ({errorMsg:'',status:123,data:[]}),
    deleteFun:(param) => ({errorMsg:'',status:123,data:[]})
}

2.3 Class(类)

与JS中的类比较相似,但是Ts赋予了更多的关键字可以让其实现更多的操作,成为一个面向对象编程的语言。下面模拟一个api的例子

interface paramStyle {
    a:number,b:number,id:string
}
interface resultStyle {
    msg:string,status:number,data:any[]
}
interface api {
    addFunc(param:paramStyle):resultStyle;
    deleteFunc(id:string|number):resultStyle;
    getFunc():resultStyle
}
abstract calss axiosCommon {
    protected basePath:string;
    private version:string = 'v1';
    pricate baseUrl:string = 'http://custom.com';
    protected axiosIns:any;
    protected datas:paramStyle[] = [
        {a:1,b:2,id:'101'},
        {a:2,b:4,id:'102'},
        {a:4,b:8,id:'103'},
        {a:8,b:16,id:'104'}
    ]
    protected constructor(basePath:string) {
        this.basePath = basePath;
        this.createAxios();
    }
    protected createAxios() {
        this.axiosIns = {
            baseUrl:`${this.baseUrl}/${this.version}/${this.basePath}`,
            get:() => {
                return {status:200 ,data:this.datas}
            },
            delete: (id:string) => {
                let index = this.datas.findIndex(d => d.id === id)
                index >=0 && this.datas.splice(index,1);
                return index >=0 ? {status:200,data:this.datas }:{status:400,data:this.datas}
            },
            add: (param:paramStyle) => {
                this.datas.push(param);
                return {status:200 ,data:this.datas}
            }
        }
    }
}
class listApi extend axiosCommon implements api {
    constructor() {
        super("listPage");
    }
    addFunc(param:paramStyle):resultStyle {
        const res = this.axiosIns.add(param);
        return {
            msg:res.status === 200 ?'success': 'fail',status:res.status,data:res.data
        }
    }
    deleteFunc(id:string):resultStyle {
        const res = this.axiosIns.delete(id);
         return {
            msg:res.status === 200 ?'success': 'fail',status:res.status,data:res.data
        }
    }
    getFunc():resultStyle {
        const res = this.axiosIns.get();
         return {
            msg:res.status === 200 ?'success': 'fail',status:res.status,data:res.data
        }
    }
}
const listApiFunc = new listApi();
listApiFunc.getFunc();
listApiFunc.addFunc( {a:16,b:32,id:'105'})
listApiFunc.deleteFunc('105');

3. 泛型<T>

  • 可以把一个泛型当成一个类型参数(是一个占位符,与函数的形参也很类似)。可以是<T>,也可以是<U>,也可以是你命名的自定义接口,类型等等,本身不具有任何意义,而是命名一个数据的时候,因为暂时不清楚该用怎样的约束规则或者一个变量可能多种数据类型的约束规则不知道该用哪个,因此做了一个占位符,等代码实际运行起来,再定义这个约束类型。
  • 泛型可以让类型约束具有可编程性,再配合其他工具,就自定义表示很多复杂数据的数据类型。
// 命名一个简单的带泛型的函数
const func = <T>(arg:T):T => {
    return arg;
}
const funcR = func<string>("ts is cool"); //"ts is cool", 表示在使用func()的时候,接收一个string的参数,并且返回一个string类型的数据。
//而这个string是我们调用函数的时候自定义传入的约束。 也可以不用特意写出来,ts会自行推导。

3.1 泛型变量

当我们使用泛型的时候,因为这只是一个占位符,并不确定是什么类型,和unkonwn很像,所以为了使用一些方法,必须对这些泛型变量,给一些约束

const func = <T>(arg:T):T => { 
    const num = arg.length;// 报错因为不确定这个是否是数组或者字符串的形参。
    return arg;// 报错因为不确定这个是否是数组或者字符串的形参。
}
// 修改为这样的即可
const func = <T>(arg:T[]):T[] => { 
    const num = arg.length;
    return arg;
}

3.2 泛型类型

既然泛型可以表示一个函数的参数和返回值的任何约束类型,那么泛型做出的函数也可以描述任何函数,给一个变量约定一个函数类型。

function func<T>(arg:T):T {
    return arg;
}
const funcT:<T>(arg: T) => T = func;
const funcT:{<T>(arg: T):T} = func; // 我们也可以使用一个字面量来描述一funcT的类型

3.3 泛型接口

既然可以使用字面量来描述一个的约束,那么这个字面量当然也可以是一个接口。、

interface funcI {
    <T>(arg: T):T
}
function func<T>(arg:T):T {
    return arg;
}
const funcT:funcI = func; 

但是有时候我们需要给这个T附加我们的类型或者类型别名, 以便于我们使用,也更加具体实用一些。

interface funcI<T> {
    (arg: T):T
}
function func<T>(arg:T):T {
    return arg;
}
const funcTNum:funcI<number> = func;  //表示这个函数参数是数字,也返回一个数字结果
const funcTStr:funcI<string> = func;  //表示这个函数参数是字符串,也返回一个字符串结果

3.4 泛型类

泛型类泛型接口比较类似,使用<>泛型类中的变量定义一个确定的类型,但是泛型接口是一个用来进行类型约束的方法。

class CreateNum<T> {
    iniNum!:T;
    add!(x:T,y:T) => T
}
const num = new CreateNum<number>()
num.iniNum = 100;
num.add = (x,y) => x+y;
num.add(num.initNum, 230);

最开始没有在类的变量后面加非空断言!,然后一直报错,简单查了一下,大概因为TypeScript 2.7 引入了一个新的控制类型检查的标记 --strictPropertyInitialization,这个参数规定每个实例属性都会在构造函数里或使用属性初始化器赋值,因此在声明一个泛型类的时候,需要给这个类的变量都赋值,但是如果给泛型变量提前赋值了,那泛型就失去了可变占位符的作用了,此时就需要在变量后面加非空断言!了,这样就告诉了TS,我们声明的这个属性和方法不是null或者undefined,只是我们暂时还没想好,给他赋什么类型的值而已

3.5 泛型约束

4. 关键字

4.1 private(私有)

4.2 public(公共)

4.3 protected(内在)

4.4 extends(继承)

4.5 abstract(虚拟抽象)

4.6 Exclude(排除)

4.7 Extract(提取)

4.8 infer(推断)

4.9 keyof

这个相当于是Ts的一个运算符,接收一个对象类型(type对象或者interface接口对象), 返回这个对象类型属性名组成的字面量联合类型;

type Dog = { name: string; age: number;  };
type D = keyof Dog; //type D = "name" | "age"
type A = keyof any; // type A = string | number | symbol;
// 遇到索引签名时,直接返回索引类型
type Dog = {  [y:number]: number  };
type dog = keyof Dog;  //type dog = number

type Doggy = {  [y:string]: boolean };
type doggy = keyof Doggy; //type doggy = string | number // 因为在对象中,默认把数字也转化成字符串使用了,所以索引签名如果类型是string的话,会转化成为 string | number联合类型。

type Doggy = {  [y:string]: unknown, [x:number]: boolean};
type doggy = keyof Doggy; //type doggy = string | number;

因为any可以是任何类型,当然他的定义里也包含对象类型,当他作为一个对象类型的时候,他自身的key值就是 string | number | symbol,因为我们定义一个对象的时候,对象的key只能是字符串,数字,或者symbol。keyof any经常用来验证一个对象的key值是否合法。

4.10 typeof

typeof操作符用于获取变量的类型,因此操作符后面接的始终是一个变量。

const m = {a:1123,b:'str',c:{m:1,w:true}}
type P = typeof m; // 相当于  type P = {a:number,b:string,c:{m:number,w:boolean}}
const arr1 = [{m:1},{m:'ss'},{b:true}];
const arr2 = ['111','333',true,999,() => 123,[]];
type P1 = typeof arr1[number]['m']; //  相当于  type P = number | string | undefined
type P2 = typeof arr2[number]; //  相当于  type P = string | number |boolean | (() => number) | never[]

4.11 in

经常与联合类型一起使用,然后对联合类型进行迭代,相当于时for..in

type a = 'age'|'name'|'gender';
type stu = {
    [k in a]:string
} // type stu = { age:string;name:string;gender:string}


7. 索引签名

虽然可以使用interface来描述一个具体的对象的,但是在大部分时候,我们创建一个对象,只能约束这个对象内部属性名的类型和属性值的类型以及我们已知的属性名和属性值,而无法预估这个对象有多少个属性,这个时候就需要索引签名来帮助描述了。

7.1 描述数组

interface arr {
    [index:number]: string // index 可以是任何数字字符串(即使你写的是数字也会隐式转化为数字字符串的)或者数字,他只是一个占位符而已。
} //这个接口可以描述一个数字属性值的对象,也可以直接描述数组
const arrlist:arr = ['a','b','1'];
const numObj :arr = { 1:'s',2:'3' };

7.2 描述对象

只有上面的这种情况才可以描述一个数组,如果是有必选的具体的属性值就不能描述数组了

interface obj {
    [index:number]:string,
    a:string
}
interface obj2 {
    [key:string]:string,
    a:string
}

6. 自定义类型

使用泛型结合关键字,接口,类型和继承等工具,组合成自定义的类型,用以适应对变量类型约束的需要。