• TypeScript深度掌握


    我们用什么去管理自己的项目,在前几年中,我们使用eslint,用prop-types插件去定义参数的类型,当我接触的时候,我本人是非常非常抵触的,为什么呢?因为我个人觉得非常的麻烦,我现在还记得,当时在老项目装接入eslint满篇爆红,去查为何爆红的场景,想想真是惨绝人寰~

    之后接触到TS,一开始本人也是非常抵触,但抱着试一试的态度,去学习,使用它,你会发现越用越好用,甚至觉得不用TS写代码都不爽了,当然有了TS你就可以远离eslint 和prop-types了。

    如果你准备接触TS,或者刚接触,对TS不太理解相信这篇文章一定能更好的帮助你,希望大家多多支持~

    先来看看知识图,如果你对以下概念有盲区,那么这篇文章应该能很好的帮助到你~

    TS 是什么 ?

    TS:是TypeScript的简称,是一种由微软开发的自由和开源的编程语言。

    TS和JS的关系

    对比与JS,TS是JS的超集,简单的说就是在 JavaScript 的基础上加入了类型系统,让每个参数都有明确的意义,从而带来了更加智能的提示。

    相对于JS而言,TS属于强类型语言,所以对于项目而言,会使代码更加规范,从而解决了大型项目代码的复杂性,其次,浏览器是不识别TS的,所以在编译的时候,TS文件会先编译为JS文件。

    安装TS

    执行命令:

    1. $ npm install -g typescript 
    2.  //或
    3. $ yarn global add typescript

    查看版本

    $ tsc -v
    

    编译

    1. $ tsc test.ts
    2. # test.ts => test.js

    在线编译

    我们为了方便起见,可以使用线上的编辑器:TypeScript Playground,像这样

     image.png

    并且你还可以看看生成对应的ts转化ES5ES6之后的代码,也有相关的例子供你查看

    TS的基本数据类型

    这里将TS的数据类型简单的进行下归类:

    • 基本类型:stringnumberbooleansymbolbigintnullundefined

    • 引用类型:array、 Tuple(元组)、 object(包含Object{})、function

    • 特殊类型:anyunknowvoidnerverEnum(枚举)

    • 其他类型:类型推理字面量类型交叉类型

    注:案例中有可能用到typeinterface,在下面会详细讲解,有比较模糊的可以先看看

    基本类型

    1.     //字符串
    2.     let str: string = "Domesy"
    3.     
    4.     // 数字
    5.     let num: number = 7
    6.     
    7.     //布尔
    8.     let bool: boolean = true
    9.     
    10.     //symbol
    11.     let sym: symbol = Symbol();
    12.      
    13.     //bigint
    14.     let big: bigint = 10n
    15.         
    16.     //null
    17.     let nunull = null
    18.     
    19.     //undefined
    20.     let unundefined = undefined

    需要注意:

    • null 和 undefined 两个类型一旦赋值上,就不能在赋值给任何其他类型

    • symbol是独一无二的,假设再定义一个 sym1,那么sym === sym1 为 false

    引用类型

    Array

    两种方式:

    • 类型名称 + []

    • Array<数据类型>

    1.     let arr1: number[] = [123]
    2.     
    3.     let arr2Array = [123]
    4.     
    5.     let arr2Array = [12'3'// error
    6.     
    7.      //要想是数字类型或字符串类型,需要使用 |
    8.     let arr3Array = [12'3'//ok

    Tuple(元组)

    Tuple 可以说是 Array 的一种特殊情况,针对上面的 arr3,我们看他的类型可以是string也可以是number,但对每个元素没有作出具体的限制。

    那么 Tuple 的作用就是限制元素的类型并且限制个数的数组,同时 Tuple这个概念值存在于TS,在JS上是不存在的

    这里存在一个问题:在TS中,是允许对 Tuple 扩增的(也就是允许使用 push方法),但在访问上不允许

    1.     let t: [number, string] = [1'2'// ok
    2.     let t1: [number, string] = [13// error
    3.     let t2: [number, string] = [1// error
    4.     let t3: [number, string] = [1'1'true// error
    5.     let t5: [number, string] = [1'2'// ok
    6.     t.push(2)
    7.     console.log(t) // [1, '2', 2]
    8.     let a =  t[0// ok
    9.     let b = t[1// ok
    10.     let c = t[2// error

    object

    • object 非原始类型,在定义上直接使用 object 是可以的,但你要更改对象的属性就会报错,原因是并没有使对象的内部具体的属性做限制,所以需要使用 {} 来定义内部类型

    1.     let obj1: object = { a1b2}
    2.     obj1.a = 3 // error
    3.     let obj2: { a: number, b: number } = {a1b2}
    4.     obj2.a = 3 // ok
    • Object(大写的O),代表所有的原始类型或非原始类型都可以进行赋值,除了null和`undefined

    1.     let objObject;
    2.     obj = 1// ok
    3.     obj = "a"// ok
    4.     obj = true// ok
    5.     obj = {}; // ok
    6.     obj = Symbol() //ok
    7.     obj = 10n //ok
    8.     obj = null// error
    9.     obj = undefined// error

    function

    定义函数

    • 有两种方式,一种为 function, 另一种为箭头函数

    • 在书写的时候,也可以写入返回值的类型,如果写入,则必须要有对应类型的返回值,但通常情况下是省略,因为TS的类型推断功能够正确推断出返回值类型

    1.     function setName1(name: string) { //ok
    2.       console.log("hello", name);
    3.     }
    4.     setName1("Domesy"); // "hello",  "Domesy"
    5.     function setName2(name: string):string { //error
    6.       console.log("hello", name);
    7.     }
    8.     setName2("Domesy");
    9.     function setName3(name: string):string { //error
    10.       console.log("hello", name);
    11.       return 1
    12.     }
    13.     setName3("Domesy");
    14.     function setName4(name: string): string { //ok
    15.       console.log("hello", name);
    16.       return name
    17.     }
    18.     setName4("Domesy"); // "hello",  "Domesy"
    19.     //箭头函数与上述同理
    20.     const setName5 = (name:string) => console.log("hello", name);
    21.     setName5("Domesy"// "hello",  "Domesy"

    参数类型

    • 可选参数:如果函数要配置可有可无的参数时,可以通过 ? 实现,切可选参数一定要在最后面

    • 默认参数:函数内可以自己设定其默认参数,用 = 实现

    • 剩余参数:仍可以使用扩展运算符 ...

    1.     // 可选参数
    2.     const setInfo1 = (name: string, age?: number) => console.log(name, age)
    3.     setInfo1('Domesy'//"Domesy",  undefined
    4.     setInfo1('Domesy'7//"Domesy",  7
    5.     // 默认参数
    6.     const setInfo2 = (name: string, age: number = 11) => console.log(name, age)
    7.     setInfo2('Domesy'//"Domesy",  11
    8.     setInfo2('Domesy'7//"Domesy",  7
    9.     // 剩余参数
    10.     const allCount = (...numbers: number[]) => console.log(`数字总和为:${numbers.reduce((val, item) => (val += item), 0)}`)
    11.     allCount(123//"数字总和为:6"

    函数重载

    函数重载:是使用相同名称和不同参数数量或类型创建多个方法的一种能力。在 TypeScript 中,表现为给同一个函数提供多个函数类型定义。简单的说:可以在同一个函数下定义多种类型值,总后汇总到一块

    1.     let obj: any = {};
    2.     function setInfo(val: string): void;
    3.     function setInfo(val: number): void;
    4.     function setInfo(val: boolean): void;
    5.     function setInfo(val: string | number | boolean): void {
    6.       if (typeof val === "string") {
    7.         obj.name = val;
    8.       } else {
    9.         obj.age = val;
    10.       }
    11.     }
    12.     setInfo("Domesy");
    13.     setInfo(7);
    14.     setInfo(true);
    15.     console.log(obj); // { name: 'Domesy', age: 7 }

    特殊类型

    any

    在 TS 中,任何类型都可以归于 any 类型,所以any类型也就成了所有类型的顶级类型,同时,如果不指定变量的类型,则默认为any类型, 当然不推荐使用该类型,因为这样丧失了TS的作用

    1.     let d:any; //等价于 let d 
    2.     d = '1';
    3.     d = 2;
    4.     d = true;
    5.     d = [123];
    6.     d = {}

    unknow

    any一样,都可以作为所有类型的顶级类型,但 unknow更加严格,那么可以说除了any 之下的第二大类型,接下来对比下any,主要严格于一下两点:

    • unknow会对值进行检测,而类型any不会做检测操作,说白了,any类型可以赋值给任何类型,但unknow只能赋值给unknow类型和any类型

    • unknow不允许定义的值有任何操作(如 方法,new等),但any可以

    1.     let u:unknown;
    2.     let a: any;
    3.     u = '1'//ok
    4.     u = 2//ok
    5.     u = true//ok
    6.     u = [123]; //ok
    7.     u = {}; //ok
    8.     let value:any = u //ok
    9.     let value1:any = a //ok
    10.     let value2:unknown = u //ok
    11.     let value3:unknown = a //ok
    12.     let value4:string = u //error
    13.     let value5:string = a //ok
    14.     let value6:number = u //error
    15.     let value7:number = a //ok
    16.     let value8:boolean = u //error
    17.     let value9:boolean = a //ok
    18.     u.set() // error
    19.     a.set() //ok
    20.     u() // error
    21.     a() //ok
    22.     new u() // error
    23.     new a() //ok

    void

    当一个函数,没有返回值时,TS会默认他的返回值为 void 类型

    1.     const setInfo = ():void => {} // 等价于 const setInfo = () => {}
    2.     const setInfo1 = ():void => { return '1' }  // error
    3.     const setInfo2 = ():void => { return 2 } // error
    4.     const setInfo3 = ():void => { return true } // error
    5.     const setInfo4 = ():void => { return  } // ok
    6.     const setInfo5 = ():void => { return undefined } //ok 

    never

    表示一个函数永远不存在返回值,TS会认为类型为 never,那么与 void 相比, never应该是 void子集, 因为 void实际上的返回值为 undefined,而 never 连 undefined也不行

    符合never的情况有:当抛出异常的情况和无限死循环

    1.     let error = ():never => { // 等价约 let error = () => {}
    2.             throw new Error("error");
    3.     };
    4.     let error1 = ():never => {
    5.         while(true){}
    6.     }

    Enum(枚举)

    可以定义一些带名字的常量,这样可以清晰表达意图创建一组有区别的用例

    注意:

    • 枚举的类型只能是 string 或 number

    • 定义的名称不能为关键字

    同时我们可以看看翻译为ES5是何样子

    数字枚举

    • 枚组的类型默认为数字类型,默认从0开始以此累加,如果有设置默认值,则只会对下面的值产生影响

    • 同时支持反向映射(及从成员值到成员名的映射),但智能映射无默认值的情况,并且只能是默认值的前面

    image.png

    字符串枚举

    字符串枚举要注意的是必须要有默认值,不支持反向映射

      

    image.png

    常量枚举

    除了数字类型字符串类型之外,还有一种特殊的类型,那就是常量枚组,也就是通过const去定义enum,但这种类型不会编译成任何 JS,只会编译对应的值

    image.png

    异构枚举

    包含了 数字类型 和 字符串类型 的混合,反向映射一样的道理

    image.png

    类型推论

    我们在学完这些基础类型,我们是不是每个类型都要去写字段是什么类型呢?其实不是,在TS中如果不设置类型,并且不进行赋值时,将会推论为any类型,如果进行赋值就会默认为类型

    1.     let a; // 推断为any
    2.     let str = '小杜杜'// 推断为string
    3.     let num = 13// 推断为number
    4.     let flag = false// 推断为boolean
    5.     str = true // error Type 'boolean' is not assignable to type 'string'.(2322)
    6.     num = 'Domesy' // error
    7.     flag = 7 // error

    字面量类型

    字面量类型:在TS中,我们可以指定参数的类型是什么,目前支持字符串数字布尔三种类型。比如说我定义了 str 的类型是 '小杜杜' 那么str的值只能是小杜杜

    1.     let str:'小杜杜' 
    2.     let num1 | 2 | 3 = 1
    3.     let flag:true
    4.     str = '小杜杜' //ok
    5.     str = 'Donmesy' // error
    6.     num = 2 //ok
    7.     num = 7 // error
    8.     flag = true // ok
    9.     flag = false // error

    交叉类型(&)

    交叉类型:将多个类型合并为一个类型,使用&符号连接,如:

    1.     type AProps = { a: string }
    2.     type BProps = { b: number }
    3.     type allProps = AProps & BProps
    4.     const Info: allProps = {
    5.         a'小杜杜',
    6.         b7
    7.     }

    同名基础属性合并

    我们可以看到交叉类型是结合两个属性的属性值,那么我们现在有个问题,要是两个属性都有相同的属性值,那么此时总的类型会怎么样,先看看下面的案列:

    1.     type AProps = { a: string, c: number }
    2.     type BProps = { b: number, c: string }
    3.     type allProps = AProps & BProps
    4.     const Info: allProps = {
    5.         a'小杜杜',
    6.         b7,
    7.         c:  1// error (property) c: never
    8.         c:  'Domesy'// error (property) c: never
    9.     }

    如果是相同的类型,合并后的类型也是此类型,那如果是不同的类型会如何:

    我们在ApropsBProps中同时加入c属性,并且c属性的类型不同,一个是number类型,另一个是string类型

    现在结合为 allProps 后呢? 是不是c属性是 number 或 string 类型都可以,还是其中的一种?

    然而在实际中, c 传入数字类型字符串类型都不行,我么看到报错,现实的是 c的类型是 never

    这是因为对应 c属性而言是 string & number,然而这种属性明显是不存在的,所以c的属性是never

    同名非基础属性合并

    1.     interface A { a: number }
    2.     interface B { b: string }
    3.     interface C {
    4.         x: A
    5.     }
    6.     interface D {
    7.         x: B
    8.     }
    9.     type allProps = C & D
    10.     const Info: allProps = {
    11.       x: {
    12.         a7,
    13.         b'小杜杜'
    14.       }
    15.     }
    16.     console.log(Info// { x: { "a": 7, "b": "小杜杜" }}

    我们来看看案例,对于混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合。

    如果 接口A 中的 也是 b,类型为number,就会跟同名基础属性合并一样

    Class(类)

    ES6中推出了一个叫 class(类) 的玩意,具体定义就不说了,相信用过React的小伙伴一定不陌生.

    基本方法

    在基本方法中有:静态属性静态方法成员属性成员方法构造器get set方法,接下来逐个看看:

    需要注意的是:在成员属性中,如果不给默认值,并且不使用是会报错的,如果不想报错就给如 **!**,如:name4!:string

    1.     class Info {
    2.       //静态属性
    3.       static name1: string = 'Domesy'
    4.       //成员属性,实际上是通过public上进行修饰,只是省略了
    5.       nmae2:string = 'Hello' //ok 
    6.       name3:string //error
    7.       name4!:string //ok 不设置默认值的时候必须加入 !
    8.       //构造方法
    9.       constructor(_name:string){
    10.         this.name4 = _name
    11.       }
    12.       //静态方法
    13.       static getName = () => {
    14.         return '我是静态方法'
    15.       }
    16.       //成员方法
    17.       getName4 = () => {
    18.         return `我是成员方法:${this.name4}`
    19.       }
    20.       //get 方法
    21.       get name5(){
    22.         return this.name4
    23.       }
    24.       //set 方法
    25.       set name5(name5){
    26.         this.name4 = name5
    27.       }
    28.     }
    29.     const setName = new Info('你好')
    30.     console.log(Info.name1//  "Domesy" 
    31.     console.log(Info.getName()) // "我是静态方法" 
    32.     console.log(setName.getName4()) // "我是成员方法:你好" 

    让我们看看上述代码翻译成ES5是什么样:

    1.     "use strict";
    2.     var Info = /** @class */ (function () {
    3.         //构造方法
    4.         function Info(_name) {
    5.             var _this = this;
    6.             //成员属性
    7.             this.nmae2 = 'Hello'//ok
    8.             //成员方法
    9.             this.getName4 = function () {
    10.                 return "\u6211\u662F\u6210\u5458\u65B9\u6CD5:".concat(_this.name4);
    11.             };
    12.             this.name4 = _name;
    13.         }
    14.         Object.defineProperty(Info.prototype"name5", {
    15.             //get 方法
    16.             getfunction () {
    17.                 return this.name4;
    18.             },
    19.             //set 方法
    20.             setfunction (name5) {
    21.                 this.name4 = name5;
    22.             },
    23.             enumerablefalse,
    24.             configurabletrue
    25.         });
    26.         //静态属性
    27.         Info.name1 = 'Domesy';
    28.         //静态方法
    29.         Info.getName = function () {
    30.             return '我是静态方法';
    31.         };
    32.         return Info;
    33.     }());
    34.     var setName = new Info('你好');
    35.     console.log(Info.name1); //  "Domesy" 
    36.     console.log(Info.getName()); // "我是静态方法" 
    37.     console.log(setName.getName4()); // "我是成员方法:你好" 

    私有字段(#)

    在 TS 3.8版本便开始支持ECMACMAScript的私有字段。

    需要注意的是私有字段与常规字段不同,主要的区别是:

    • 私有字段以 # 字符开头,也叫私有名称;

    • 每个私有字段名称都唯一地限定于其包含的类;

    • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);

    • 私有字段不能在包含的类之外访问,甚至不能被检测到。

    1.     class Info {
    2.       #name: string; //私有字段
    3.       getName: string;
    4.       constructor(name: string) {
    5.         this.#name = name;
    6.         this.getName = name
    7.       }
    8.       setName() {
    9.         return `我的名字是${this.#name}`
    10.       }
    11.     }
    12.     let myName = new Info("Domesy");
    13.     console.log(myName.setName()) // "我的名字是Domesy" 
    14.     console.log(myName.getName// ok "Domesy" 
    15.     console.log(myName.#name) // error 
    16.     // Property '#name' is not accessible outside class 'Info' 
    17.     // because it has a private identifier.(18013)

    只读属性(readonly)

    只读属性:用 readonly修饰,只能在构造函数中初始化,并且在TS中,只允许将interfacetypeclass上的属性标识为readonly

    • readonly实际上只是在编译阶段进行代码检查

    • radonly修饰的词只能在 constructor阶段修改,其他时刻不允许修改

    1.     class Info {
    2.       public readonly name: string; // 只读属性
    3.       name1:string
    4.       constructor(name: string) {
    5.         this.name = name;
    6.         this.name1 = name;
    7.       }
    8.       setName(name:string) {
    9.         this.name = name // error
    10.         this.name1 = name; // ok
    11.       }
    12.     }

    继承(extends)

    继承:是个比较重要的点,指的是子可以继承父的思想,也就是说 子类 通过继承父类后,就拥有了父类的属性和方法,这点与HOC有点类似

    这里又个super字段,给不知道的小伙伴说说,其作用是调用父类上的属性和方法

    1.     // 父类
    2.     class Person {
    3.       name: string
    4.       age: number
    5.       constructor(name: string, age:number){
    6.         this.name = name
    7.         this.age = age
    8.       }
    9.       getName(){
    10.         console.log(`我的姓名是:${this.name}`)
    11.         return this.name
    12.       }
    13.       setName(name: string){
    14.         console.log(`设置姓名为:${name}`)
    15.         this.name = name
    16.       }
    17.     }
    18.     // 子类
    19.     class Child extends Person {
    20.       tel: number
    21.       constructor(name: string, age: number, tel:number){
    22.         super(name, age)
    23.         this.tel = tel
    24.       }
    25.       getTel(){
    26.         console.log(`电话号码是${this.tel}`)
    27.         return this.tel
    28.       }
    29.     }
    30.     let res = new Child("Domesy"7 , 123456)
    31.     console.log(res) // Child {."name": "Domesy", "age": 7, "no": 1 }
    32.     console.log(res.age// 7
    33.     res.setName('小杜杜'// "设置姓名为:小杜杜" 
    34.     res.getName() //   "我的姓名是:小杜杜"
    35.     res.getTel() //  "电话号码是123456" 

    修饰符

    主要有三种修饰符:

    • public:类中、子类内的任何地方、外部都能调用

    • protected:类中、子类内的任何地方都能调用,但外部不能调用

    • private:类中可以调用,子类内的任何地方、外部均不可调用

    1.     class Person {
    2.       public name: string
    3.       protected age: number
    4.       private tel: number
    5.       constructor(name: string, age:number, tel: number){
    6.         this.name = name
    7.         this.age = age
    8.         this.tel = tel
    9.       }
    10.     }
    11.     class Child extends Person {
    12.       constructor(name: string, age: number, tel: number) {
    13.         super(name, age, tel);
    14.       }
    15.       getName(){
    16.         console.log(`我的名字叫${this.name},年龄是${this.age}`// ok name 和 age可以
    17.         console.log(`电话是${this.tel}`// error 报错 原因是 tel 拿不出来
    18.       }
    19.     }
    20.     const res = new Child('Domesy'7123456)
    21.     console.log(res.name// ok Domesy
    22.     console.log(res.age// error
    23.     console.log(res.tel// error

    abstract

    abstract: 用abstract关键字声明的类叫做抽象类,声明的方法叫做抽象方法

    • 抽象类:指不能被实例化,因为它里面包含一个或多个抽象方法。

    • 抽象方法:是指不包含具体实现的方法;

    注:抽象类是不能直接实例化,只能实例化实现了所有抽象方法的子类

    1.     abstract class Person {
    2.       constructor(public name: string){}
    3.       // 抽象方法
    4.       abstract setAge(age: number) :void;
    5.     }
    6.     class Child extends Person {
    7.       constructor(name: string) {
    8.         super(name);
    9.       }
    10.       setAge(age: number): void {
    11.         console.log(`我的名字是${this.name},年龄是${age}`);
    12.       }
    13.     }
    14.     let res = new Person("小杜杜"//error
    15.     let res1 = new Child("小杜杜");
    16.     res1.setAge(7// "我的名字是小杜杜,年龄是7"

    重写和重载

    • 重写:子类重写继承自父类中的方法

    • 重载:指为同一个函数提供多个类型定义,与上述函数的重载类似

    1.     // 重写
    2.     class Person{
    3.       setName(name: string){
    4.         return `我的名字叫${name}`
    5.       }
    6.     }
    7.     class Child extends Person{
    8.       setName(name: string){
    9.         return `你的名字叫${name}`
    10.       }
    11.     }
    12.     const yourName = new Child()
    13.     console.log(yourName.setName('小杜杜')) // "你的名字叫小杜杜" 
    14.     // 重载
    15.     class Person1{
    16.       setNameAge(name: string):void;
    17.       setNameAge(name: number):void;
    18.       setNameAge(name:string | number){
    19.         if(typeof name === 'string'){
    20.           console.log(`我的名字是${name}`)
    21.         }else{
    22.           console.log(`我的年龄是${name}`)
    23.         }
    24.       };
    25.     }
    26.     const res = new Person1()
    27.     res.setNameAge('小杜杜'// "我的名字是小杜杜" 
    28.     res.setNameAge(7// "我的年龄是7"

    TS断言和类型守卫

    TS断言

    分为三种:类型断言非空断言确定赋值断言

    当断言失效后,可能使用到:双重断言

    类型断言

    在特定的环境中,我们会比TS知道这个值具体是什么类型,不需要TS去判断,简单的理解就是,类型断言会告诉编译器,你不用给我进行检查,相信我,他就是这个类型

    共有两种方式:

    • 尖括号

    • as:推荐

    1.    //尖括号
    2.    let num:any = '小杜杜'
    3.    let res1: number = (num).length// React中会 error
    4.    // as 语法
    5.    let str: any = 'Domesy';
    6.    let res: number = (str as string).length;

    但需要注意的是:尖括号语法在React中会报错,原因是与JSX语法会产生冲突,所以只能使用as语法

    非空断言

    在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。

    我们对比下ES5的代码

    image.png

    我们可以看出来 !可以帮助我们过滤 null和 undefined类型,也就是说,编译器会默认我们只会传来string类型的数据,所以可以赋值为str1

    但变成ES5后 !会被移除,所以当传入 null 的时候,还是会打出 null

    确定赋值断言

    TS 2.7版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,以告诉TS该属性会被明确赋值。

    1.     let num: number;
    2.     let num1!: number;
    3.     const setNumber = () => num = 7
    4.     const setNumber1 = () => num1 = 7
    5.     setNumber()
    6.     setNumber1()
    7.     console.log(num) // error 
    8.     console.log(num1) // ok

    双重断言

    断言失效后,可能会用到,但一般情况下不会使用

    失效的情况:基础类型不能断言为接口

    1.     interface Info{
    2.       name: string;
    3.       age: number;
    4.     }
    5.     const name = '小杜杜' as Info// error, 原因是不能把 string 类型断言为 一个接口
    6.     const name1 = '小杜杜' as any as Info//ok

    类型守卫

    类型守卫:是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内

    我个人的感觉是,类型守卫就是你可以设置多种类型,但我默认你是什么类型的意思

    目前,常有的类型守卫共有4种:in关键字typeof关键字interfaceof关键字类型谓词(is)

    in关键字

    用于判断这个属性是那个里面的

    1.     interface Info {
    2.       name: string
    3.       age: number
    4.     }
    5.     interface Info1{
    6.       name: string
    7.       flagetrue
    8.     }
    9.     const setInfo = (data: Info | Info1) => {
    10.       if("age" in data){
    11.         console.log(`我的名字是:${data.name},年龄是:${data.age}`)
    12.       }
    13.        if("flage" in data){
    14.         console.log(`我的名字是:${data.name},性别是:${data.flage}`)
    15.       }
    16.     }
    17.     setInfo({name'小杜杜'age7}) // "我的名字是:小杜杜,年龄是:7" 
    18.     setInfo({name'小杜杜'flagetrue}) // "我的名字是:小杜杜,性别是:true"

    typeof关键字

    用于判断基本类型,如string | number等

    1.     const setInfo = (data: number | string | undefined) => {
    2.       if(typeof data === "string"){
    3.         console.log(`我的名字是:${data}`)
    4.       }
    5.       if(typeof data === "number"){
    6.         console.log(`我的年龄是:${data}`)
    7.       }
    8.       if(typeof data === "undefined"){
    9.         console.log(data)
    10.       }
    11.     }
    12.     setInfo('小杜杜'// "我的名字是:小杜杜"  
    13.     setInfo(7// "我的年龄是:7" 
    14.     setInfo(undefined// undefined" 

    interfaceof关键字

    用于判断一个实例是不是构造函数,或使用类的时候

    1.     class Name {
    2.       name: string = '小杜杜'
    3.     }
    4.     class Age extends Name{
    5.       age: number = 7
    6.     }
    7.     const setInfo = (data: Name) => {
    8.       if (data instanceof Age) {
    9.         console.log(`我的年龄是${data.age}`);
    10.       } else {
    11.         console.log(`我的名字是${data.name}`);
    12.       }
    13.     } 
    14.     setInfo(new Name()) // "我的名字是小杜杜"
    15.     setInfo(new Age()) // "我的年龄是7" 

    类型谓词(is)

    1. function isNumber(x: any): x is number { //默认传入的是number类型
    2.   return typeof x === "number"
    3. }
    4. console.log(isNumber(7)) // true
    5. console.log(isNumber('7')) //false
    6. console.log(isNumber(true)) //false

    两者的区别

    通过上面的介绍,我们可以发现断言类型守卫的概念非常相似,都是确定参数的类型,但断言更加霸道,它是直接告诉编辑器,这个参数就是这个类型,而类型守卫更像确定这个参数具体是什么类型。(个人理解,有不对的地方欢迎指出~)

    类型别名、接口

    类型别名(type)

    类型别名:也就是type,用来给一个类型起个新名字

    1.     type InfoProps = string | number
    2.     
    3.     const setInfo = (data: InfoProps) => {}

    接口(interface)

    接口:在面向对象语言中表示行为抽象,也可以用来描述对象的形状。

    使用interface关键字来定义接口

    对象的形状

    接口可以用来描述对象,主要可以包括以下数据:可读属性只读属性任意属性

    • 可读属性:当我们定义一个接口时,我们的属性可能不需要全都要,这是就需要 ? 来解决

    • 只读属性:用 readonly修饰的属性为只读属性,意思是指允许定义,不允许之后进行更改

    • 任意属性:这个属性极为重要,它是可以用作就算没有定义,也可以使用,比如 [data: string]: any。比如说我们对组件进行封装,而封装的那个组件并没有导出对应的类型,然而又想让他不报错,这时就可以使用任意属性

    1.     interface Props {
    2.         a: string;
    3.         b: number;
    4.         c: boolean;
    5.         d?: number; // 可选属性
    6.         readonly e: string; //只读属性
    7.         [f: string]: any //任意属性
    8.     }
    9.     let resProps = {
    10.         a'小杜杜',
    11.         b7,
    12.         ctrue,
    13.         e'Domesy',
    14.         d1// 有没有d都可以
    15.         h2 // 任意属性,之前为定义过h
    16.     }
    17.     let res.e = 'hi' // error, 原因是可读属性不允许更改

    继承

    继承:与类一样,接口也存在继承属性,也是使用extends字段

    1.     interface nameProps {
    2.         name: string
    3.     }
    4.     interface Props extends nameProps{
    5.         age: number
    6.     }
    7.     const resProps = {
    8.         name'小杜杜',
    9.         age7
    10.     }

    函数类型接口

    同时,可以定义函数和类,加new修饰的事,不加new的事函数

    1.     interface Props {
    2.         (data: number): number
    3.     }
    4.     const infoProps = (number:number) => number  //可定义函数
    5.     // 定义函数
    6.     class A {
    7.         name:string
    8.         constructor(name: string){
    9.             this.name = name
    10.         }
    11.     }
    12.     interface PropsClass{
    13.         new (name: string): A
    14.     }
    15.     const info1 = (fun: PropsClass, name: string) => new fun(name)
    16.     const res = info1(A, "小杜杜")
    17.     console.log(res.name// "小杜杜" 

    type 和 interface 的区别

    通过上面的学习,我们发现类型别名接口非常相似,可以说在大多数情况下,typeinterface是等价的

    但在一些特定的场景差距还是比较大的,接下来逐个来看看

    基础数据类型

    • typeinterface都可以定义 对象 和 函数

    • type可以定义其他数据类型,如字符串、数字、元祖、联合类型等,而interface不行

    1.     type A = string // 基本类型
    2.     type B = string | number // 联合类型
    3.     type C = [number, string] // 元祖
    4.     const dom = document.createElement("div");  // dom元素
    5.     type D = typeof dom

    扩展

    interface 可以扩展 typetype 也可以扩展为 interface,但两者实现扩展的方式不同。

    • interface 是通过 extends 来实现

    • type 是通过 & 来实现

    1.     // interface 扩展 interface
    2.     interface A {
    3.         a: string
    4.     }
    5.     interface B extends  A {
    6.         b: number
    7.     }
    8.     const obj:B = { a`小杜杜`b7 }
    9.     // type 扩展 type
    10.     type C = { a: string }
    11.     type D = C & { b: number }
    12.     const obj1:D = { a`小杜杜`b7 }
    13.     // interface 扩展为 Type
    14.     type E = { a: string }
    15.     interface F extends E { b: number }
    16.     const obj2:F = { a`小杜杜`b7 }
    17.     // type 扩展为 interface
    18.     interface G { a: string }
    19.     type H = G & {b: number}
    20.     const obj3:H = { a`小杜杜`b7 }

    重复定义

    interface 可以多次被定义,并且会进行合并,但type不行

    1.     interface A {
    2.         a: string
    3.     }
    4.     interface A {
    5.         b: number
    6.     }
    7.     const obj:A = { a`小杜杜`b7 }
    8.     type B = { a: string }
    9.     type B = { b: number } // error

    联合类型(Union Types)

    联合类型(Union Types): 表示取值可以为多种类型中的一种,未赋值时联合类型上只能访问两个类型共有的属性和方法,如:

    1.     const setInfo = (name: string | number) => {}
    2.     setInfo('小杜杜')
    3.     setInfo(7)

    从上面看 setInfo接收一个name,而 name 可以接收 stringnumber类型,那么这个参数便是联合类型

    可辨识联合

    可辨识联合:包含三个特点,分别是可辨识联合类型类型守卫,

    这种类型的本质是:结合联合类型字面量类型的一种类型保护方法。

    如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。

    也就是上面一起结合使用,这里写个小例子:

    1.     interface A {
    2.       type1,
    3.       name: string
    4.     }
    5.     interface B {
    6.       type2
    7.       age: number
    8.     }
    9.     interface C {
    10.       type3,
    11.       sex: boolean
    12.     }
    13.     // const setInfo = (data: A | B | C) => {
    14.     //   return data.type // ok 原因是 A 、B、C 都有 type属性
    15.     //   return data.age // error,  原因是没有判断具体是哪个类型,不能确定是A,还是B,或者是C
    16.     // }
    17.     const setInfo1 = (data: A | B | C) => {
    18.       if (data.type === 1 ) {
    19.         console.log(`我的名字是${data.name}`);
    20.       } else if (data.type === 2 ){
    21.         console.log(`我的年龄是${data.age}`);
    22.       } else if (data.type === 3 ){
    23.         console.log(`我的性别是${data.sex}`);
    24.       }
    25.     }
    26.     setInfo1({type1name'小杜杜'}) // "我的名字是小杜杜"
    27.     setInfo1({type2age7}) // "我的年龄是7" 
    28.     setInfo1({type3sextrue}) // "我的性别是true" 

    定义了 ABC 三次接口,但这三个接口都包含type属性,那么type就是可辨识的属性,而其他属性只跟特性的接口相关。

    然后通过可辨识属性type,才能使用其相关的属性

    泛型

    泛型:Generics,是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

    也就是说,泛型是允许同一个函数接受不同类型参数的一种模版,与any相比,使用泛型来创建可服用的组件要更好,因为泛型会保留参数类型(PS:泛型是整个TS的重点,也是难点,请多多注意~)

    为什么需要泛型

    我们先看看一个例子:

    1.     const calcArray = (data:any):any[] => {
    2.         let list = []
    3.         for(let i = 0; i < 3; i++){
    4.             list.push(data)
    5.         }
    6.         return list
    7.     }
    8.     console.log(calcArray('d')) // ["d", "d", "d"]

    上述的例子我们发现,在calcArray中传任何类型的参数,返回的数组都是any类型

    由于我们不知道传入的数据是什么,所以返回的数据也为any的数组

    但我们现在想要的效果是:无论我们传什么类型,都能返回对应的类型,针对这种情况怎么办?所以此时泛型就登场了

    泛型语法

    我们先用泛型对上面的例子进行改造下,

    1.     const calcArray = (data:T):T[] => {
    2.         let list:T[] = []
    3.         for(let i = 0; i < 3; i++){
    4.             list.push(data)
    5.         }
    6.         return list
    7.     }
    8.     const res:string[] = calcArray('d'// ok
    9.     const res1:number[] = calcArray(7// ok
    10.     type Props = {
    11.         name: string,
    12.         age: number
    13.     }
    14.     const res3Props[] = calcArray<Props>({name'小杜杜'age7}) //ok

    经过上面的案例,我们发现传入的字符串数字对象,都能返回对应的类型,从而达到我们的目的,接下来我们再看看泛型语法

    1.     function identity (value:T) : T {
    2.         return value
    3.     }

    第一次看到这个我们是不是很懵,实际上这个T就是传递的类型,从上述的例子来看,这个就是,要注意一点,这个实际上是可以省略的,因为 TS 具有类型推论,可以自己推断类型

    多类型传参

    我们有多个未知的类型占位,我们可以定义任何的字母来表示不同的参数类型

    1.     const calcArray = (name:T, age:U): {name:T, age:U} => {
    2.         const res: {name:T, age:U} = {name, age}
    3.         return res
    4.     }
    5.     const res = calcArray('小杜杜'7)
    6.     console.log(res) // {"name": "小杜杜", "age": 7}

    泛型接口

    定义接口的时候,我们也可以使用泛型

    1.     interface A {
    2.         data: T
    3.     }
    4.     const Info: A = {data'1'}
    5.     console.log(Info.data// "1"

    泛型类

    同样泛型也可以定义类

    1.     class clacArray{
    2.         private arr: T[] = [];
    3.         add(value: T) {
    4.             this.arr.push(value)
    5.         }
    6.         getValue(): T {
    7.             let res = this.arr[0];
    8.             console.log(this.arr)
    9.             return res;
    10.         }
    11.     }
    12.     const res = new clacArray()
    13.     res.add(1)
    14.     res.add(2)
    15.     res.add(3)
    16.     res.getValue() //[1, 2, 3] 
    17.     console.log(res.getValue// 1

    泛型类型别名

    1.     type Info = {
    2.         name?: T
    3.         age?: T
    4.     }
    5.     const res:Info = { name'小杜杜'}
    6.     const res1:Info = { age7}

    泛型默认参数

    所谓默认参数,是指定类型,如默认值一样,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。

    1.     const calcArray = (data:T):T[] => {
    2.         let list:T[] = []
    3.         for(let i = 0; i < 3; i++){
    4.             list.push(data)
    5.         }
    6.         return list
    7.     }

    泛型常用字母

    用常用的字母来表示一些变量的代表:

    • T:代表Type,定义泛型时通常用作第一个类型变量名称

    • K:代表Key,表示对象中的键类型

    • V:代表Value,表示对象中的值类型

    • E:代表Element,表示的元素类型

    常用技巧

    在 TS 中有许多关键字和工具类型,在使用上,需要注意泛型上的应用,有的时候结合起来可能就有一定的问题

    在此特别需要注意 extendstypeofPartialRecordExcludeOmit这几个工具类型

    extends

    extends:检验是否拥有其属性 在这里,举个例子,我们知道字符串数组拥有length属性,但number没有这个属性。

    1.     const calcArray = (data:T): number => {
    2.       return data.length // error 
    3.     }

    上述的 calcArray的作用只是获取data的数量,但此时在TS中会报错,这是因为TS不确定传来的属性是否具备length这个属性,毕竟每个属性都不可能完全相同

    那么这时该怎么解决呢?

    我们已经确定,要拿到传过来数据的 length,也就是说传过来的属性必须具备length这个属性,如果没有,则不让他调用这个方法。

    换句话说,calcArray需要具备检验属性的功能,对于上述例子就是检验是否有length的功能,这是我们就需要extends这个属性帮我们去鉴定:

    1.     interface Props {
    2.         length: number
    3.     }
    4.     const calcArray = extends Props,>(data:T): number => {
    5.       return data.length // error
    6.     }
    7.     calcArray('12'// ok
    8.     calcArray([1,3]) //ok
    9.     calcArray(2//error 

    可以看出calcArray(2)会报错,这是因为number类型并不具备length这个属性

    typeof

    typeof关键字:我们在类型保护的时候讲解了typeof的作用,除此之外,这个关键字还可以实现推出类型,如下图,可以推断中 Props 包含的类型

    image.png

    keyof

    keyof关键字: 可以获取一个对象接口的所有key值,可以检查对象上的键是否存在

    1.     interface Props {
    2.         name: string;
    3.         age: number;
    4.         sex: boolean
    5.     }
    6.     type PropsKey = keyof Props//包含 name, age, sex
    7.     const res:PropsKey = 'name' // ok
    8.     const res1:PropsKey = 'tel' // error
    9.     // 泛型中的应用
    10.     const getInfo = extends keyof T>(data: T, key: K): T[K] => {
    11.         return data[key]
    12.     }
    13.     const info = {
    14.         name'小杜杜',
    15.         age7,
    16.         sextrue
    17.     }
    18.     getInfo(info, 'name'); //ok
    19.     getInfo(info, 'tel'); //error

    索引访问操作符

    索引访问操作符:通过 [] 操作符可进行索引访问,可以访问其中一个属性

    image.png

    in

    in:映射类型, 用来映射遍历枚举类型


    image.png

    infer

    infer:可以是使用为条件语句,可以用 infer 声明一个类型变量并且对它进行使用。如

    1.     type Info = T extends { a: infer U; b: infer U } ? U : never;
    2.     type Props = Info<{ a: string; b: number }>; // Props类:string | number
    3.     type Props1 = Info // Props类型:never

    Partial

    Partial语法Partial 作用:将所有属性变为可选的 ?

    1.     interface Props {
    2.         name: string,
    3.         age: number
    4.     }
    5.     const infoProps = {
    6.         name'小杜杜',
    7.         age7
    8.     }
    9.     const info1Partial<Props> = { 
    10.         name'小杜杜'
    11.     }

    从上述代码上来看,name 和 age 属于必填,对于 info 来说必须要设置 name 和 age 属性才行,但对于 info1来说,只要是个对象就可以,至于是否有name、 age属性并不重要

    Required

    Required语法Required 作用:将所有属性变为必选的,与 Partial相反

    1.     interface Props {
    2.         name: string,
    3.         age: number,
    4.         sex?: boolean
    5.     }
    6.     const infoProps = {
    7.         name'小杜杜',
    8.         age7
    9.     }
    10.     const info1Required<Props> = { 
    11.         name'小杜杜',
    12.         age7,
    13.         sextrue
    14.     }

    Readonly

    Readonly语法Readonly 作用:将所有属性都加上 readonly 修饰符来实现。也就是说无法修改

    1.     interface Props {
    2.         name: string
    3.         age: number
    4.     }
    5.     let infoReadonly<Props> = {
    6.         name'小杜杜',
    7.         age7
    8.     }
    9.     info.age = 1 //error read-only 只读属性

    从上述代码上来看, Readonly修饰后,属性无法再次更改,智能使用

    Record

    Record语法:Record

    作用:将 K 中所有的属性的值转化为 T 类型。

    1.     interface Props {
    2.         name: string,
    3.         age: number
    4.     }
    5.     type InfoProps = 'JS' | 'TS'
    6.     const InfoRecord<InfoPropsProps> = {
    7.         JS: {
    8.             name'小杜杜',
    9.             age7
    10.         },
    11.         TS: {
    12.             name'TypeScript',
    13.             age11
    14.         }
    15.     }

    从上述代码上来看, InfoProps的属性分别包含Props的属性

    需要注意的一点是:K extends keyof any其类型可以是:stringnumbersymbol

    Pick

    Pick语法:Pick

    作用:将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

    1.     interface Props {
    2.         name: string,
    3.         age: number,
    4.         sex: boolean
    5.     }
    6.     type nameProps = Pick<Props'name' | 'age'>
    7.     const info: nameProps = {
    8.         name'小杜杜',
    9.         age7
    10.     }

    从上述代码上来看, Props原本属性包括nameagesex三个属性,通过 Pick我们吧nameage挑了出来,所以不需要sex属性

    Exclude

    Exclude语法:Exclude

    作用:将T类型中的U类型剔除。

    1.     // 数字类型
    2.     type numProps = Exclude<1 | 2 | 31 | 2// 3
    3.     type numProps1 = Exclude<11 | 2// nerver
    4.     type numProps2 = Exclude<11// nerver
    5.     type numProps3 = Exclude<1 | 27// 1 2
    6.     // 字符串类型
    7.     type info = "name" | "age" | "sex"
    8.     type info1 = "name" | "age" 
    9.     type infoProps = Exclude //  "sex"
    10.     // 类型
    11.     type typeProps = Exclude() => void), Function// string | number
    12.     // 对象
    13.     type obj = { name1sextrue }
    14.     type obj1 = { name1 }
    15.     type objProps = Exclude // nerver

    从上述代码上来看,我们比较了下类型上的,当 T 中有 U 就会剔除对应的属性,如果 U 中又的属性 T 中没有,或 T 和 U 刚好一样的情况都会返回 nerver,且对象永远返回nerver

    Extra

    Extra语法:Extra

    作用:将T 可分配给的类型中提取 U。与 Exclude相反

    1.     type numProps = Extract<1 | 2 | 31 | 2// 1 | 2

    Omit

    Omit语法:Omit

    作用:将已经声明的类型进行属性剔除获得新类型

    image.png

    与 Exclude的区别:Omit 返回的是新的类型,原理上是在 Exclude之上进行的,Exclude是根据自类型返回的

    NonNullable

    NonNullable语法NonNullable 作用:从 T 中排除 null 和 undefined

    ReturnType

    ReturnType语法ReturnType

    作用:用于获取 函数T的返回类型。

    1.     type Props = ReturnType<() => string> // string
    2.     type Props1 = ReturnType<extends U, U extends number>() => T>; // number
    3.     type Props2 = ReturnType// any
    4.     type Props3 = ReturnType// any

    从上述代码上来看, ReturnType可以接受 any 和 never 类型,原因是这两个类型属于顶级类型,包含函数

    Parameters

    ParametersParameters 作用:用于获取 获取函数类型的参数类型

    1.     type Props = Parameters<() => string> // []
    2.     type Props1 = Parameters<(data: string) => void// [string]
    3.     type Props2 = Parameters// unknown[]
    4.     type Props3 = Parameters// never

    参考:

    • TypeScript 4.0

    • 深入理解 TypeScript

    • 一份不可多得的 TS 学习指南(1.8W字)

    以及网上的各种各样的资源。

    小结

    到此,有关TS的知识就已经说完了,相信掌握了这些知识,你一定会对TS有更深的理解,这篇文章按照自己的理解,进行分类,个人觉得这样的分类比较合理,如果有什么更好的建议,欢迎在评论区指出~

    想到自己刚接触TS的时候,是有点抵触的,但随着时间的推移,发现TS真的很香,并且TS也不算是很难,只要你花费一定的时间,在结合与项目,你就会发现真香定律

    相信这篇文章已经极大程度的解决了TS相关的代码,希望这篇文章能让你迅速掌握TS,喜欢的点个赞👍🏻支持下吧(● ̄(エ) ̄●)

  • 相关阅读:
    Window Anaconda 安装pytorch 启用cuda 终究手段
    实现元宇宙需面临的三大挑战
    【Java初阶】---方法与递归
    条款02:尽量以const, enum, inline替换#define
    验证ADG的坏块检测和自动修复
    【软件工程】软件工程定义、软件危机以及软件生命周期
    TCP 可靠性的关键机制 —— 确认应答机制 (ACK)
    安卓bp文件和mk文件转换
    5G NR Polar码简介(一)
    electron vue 模仿qq登录界面
  • 原文地址:https://blog.csdn.net/weixin_42066070/article/details/126527524