• TypeScript常用知识点及其最佳实践总结


    一、常用知识点

    1、概述

    TypeScript是添加了类型系统的 JavaScript,适用于任何规模的项目;
    在运行前需要先编译为 JavaScript,在编译阶段就会进行类型检查

    npm install -g typescript tsc hello.ts
    
    • 1

    TypeScript 通过:指定变量的类型;

    function sayHello(person: string) {     
        return 'Hello, ' + person; 
    }  
    let user = 'Tom'; 
    console.log(sayHello(user));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错;
    即使报错了,还是会生成编译结果,如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError

    2、数据类型

    基元类型: string、number、boolean,其中首字母大写时表示特殊内置类型

    数组: 两种表示方法,number[]、Array

    any: TS的特殊类型,可以访问它的任何属性,分配任何值,当不指定类型,且TS无法从上下文推断,即默认为any类型

    类型注解: 类型的注释总是在参数之后 let myName: string = “Casstime”

    函数: 声明函数时,可以在每个参数后添加类型注释,还可以添加返回类型注释

    function getSum(a: number, b: number): number {
       return a+b;
    }
    
    • 1
    • 2
    • 3

    函数的另一种表达方式

    let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
       return x + y; 
    };
    
    • 1
    • 2
    • 3

    这里的 => 表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型

    对象: 定义对象类型,只需列出其属性及其类型,在属性名称后添加一个 ?,表示可选属性

    联合类型: 是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一种的值,需要注意的是,如果你有联合类型string | number ,则不能只使用一种类型的操作,解决方案是类型缩小,即TS根据代码结构为值推断出更具体的类型时,就会发生缩小

    类型别名: 一般用于给一个联合类型或对象类型取新的名字

    type ID = number | string
    
    • 1

    接口: 另一种命名对象类型的方法

    interface Point {x: number;y: number;}
    
    • 1

    别名和接口不同点: type可以声明数据类型/联合类型/元组等别名、通过&运算符进行交叉操作;interface允许extend或者implement、能够合并声明

    类型断言: 用来手动指定一个值的类型,tsx 语法中

    值 as 类型
    
    • 1

    类型断言的常见用途有以下几种:
    (1)将一个联合类型断言为其中一个类型;
    (2)将一个父类断言为更加具体的子类;
    (3)将任何一个类型断言为 any;
    (4)将 any 断言为一个具体的类型;
    双重断言即是将任何一个类型断言为any,然后再断言为任何另一个类型

    文字类型: 通过将文字组合成联合,常在接受一组特定已知值的函数中使用

    function printText(s: string, alignment: "left" | "right" | "center") {
    	// ...
    }
    
    • 1
    • 2
    • 3

    非空断言运算符: ! 在任何表达式之后写入实际上是一种类型断言,即该值不是 null or undefined

    枚举: 一组命名的常量,这不是JavaScript 的类型级别的添加,而是添加到语言和运行时的内容

    enum Direction {Up = 1,Down,Left,Right,}
    
    • 1

    3、类型缩小

    (1)typeof

    typeof的返回值:

    "string""number""bigint""boolean""symbol""undefined""object""function"
    
    • 1

    在 TypeScript 中,检查 typeof 的返回值是一种类型保护

    function printAll(strs: string | string[] | number) {
       if (typeof strs === "object") {
           for (const s of strs) {
               console.log(s);     
           }
       } else if (typeof strs === "string") {
           console.log(strs);     
           } else{
               //dosomething         
           }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)真值缩小

    真值检查即是将变量强制转化为布尔值,以下值都会强制转化为false:

    0、NaN、"" (空字符串)、0n ( bigint 零的版本)、null、undefined
    
    • 1

    其他都会被强制转为true,应用场景如下

    function printAll(strs: string | string[] | null) {     
    	if (strs && typeof strs === "object") {
    		for (const s of strs) { 
    			console.log(s);     
    		}
    	} else if (typeof strs === "string") {         
    		console.log(strs);
    	}  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3)等值缩小

    typescript 也使用分支语句做 === , !== , == ,和 != 等值检查,来实现类型缩小

    (4)in操作符缩小

    用于确定对象是否具有某个名称的属性

    type Fish = { swim: () => void }; 
    type Bird = { fly: () => void }; 
    function move(animal: Fish | Bird) {     
    	if ("swim" in animal) {        
    		return animal.swim();
    	}     
    	return animal.fly(); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (5)instanceof

    检查一个值是否是另一个值的“实例”

    function logValue(x: Date | string) {     
        if (x instanceof Date) {         
            console.log(x.toUTCString());     
        } else {         
            console.log(x.toUpperCase());     
        } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4、函数

    (1)函数类型表达式

    检查一个值是否是另一个值的“实例”

    fn: (a: sting) => void
    
    • 1

    可以用一个类型别名来命名一个函数类型

    type GreetFunction = (a: string) => void; function greeter(fn: GreetFunction) {
         // ...						 
    }
    
    • 1
    • 2
    • 3

    (2)调用签名

    函数还可以有属性,通过在一个对象类型中写一个调用签名,如下所示函数fun有一个属性property,其接受一个参数arg,返回类型为boolean

    type fun = {     
    	property: string;
    	(arg: number) : boolean; 
    }
    
    • 1
    • 2
    • 3
    • 4

    (3)构造签名

    通过在调用签名前添加new关键字来写一个构造签名

    class Ctor {   
        s: string   
        constructor(s: string) {     
            this.s = s    
        } 
    } 
    type SomeConstructor = {   new (s: string): Ctor } 
    function fn(ctor: SomeConstructor) {   
        return new ctor("hello") 
    } 
    const f = fn(Ctor) console.log(f.s)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (4)泛型函数

    当输入的类型与输出的类型有关,可以使用泛型函数

    function fun(arr: T[]): T | undefined {     return arr[0] }
    
    • 1

    可以使用一个约束条件来限制一个类型参数可以接受的类型,(如下所示,限制longest函数入参必须有length属性)

    function longest(a: Type, b: Type) {   
        if (a.length >= b.length) {    
            return a;   
         } else {     
             return b; 
         } 
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (5)可选参数

    通过?标记可选参数

    function f(x?: number) { // ...}
    
    • 1

    (6)函数重载

    在TypeScript中,可以通过编写重载签名来指定一个可以以不同方式调用的函数

    function makeDate(timestamp: number): Date; 
    function makeDate(m:number, d: number, y:number): Date;
    
    • 1
    • 2

    5、对象类型

    对象是分组和传递数组的基本方式,对象可以是匿名的,也可以通过interface或者type命名

    (1)属性

    通过?后缀标记为可选属性,前缀readonly标记只读属性

    interface SomeType { 
    	readonlt prop : string;
    	a?: number 
    }
    
    • 1
    • 2
    • 3
    • 4

    不知道一个类型的所有属性名称时,可以使用一个索引签名来描述可能的值的类型

    interface StringArray { 
    	[index: number]: string 
    }
    
    • 1
    • 2
    • 3

    (2)扩展签名

    通过接口的extends关键字,可以有效地从其他命名的类型中复制成员,并添加我们想要的任何新的成员

    (3)交叉类型

    交叉类型是用 & 操作符定义的,可以组合现有的对象类型

    interface Colorful {
        color: string 
    } 
    interface: Circle {
        radius: number 
    }  
    type ColorfulCircle = colorful & Circle
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (4)泛型对象类型

    interface Box {
        contents: T 
    }
    
    • 1
    • 2
    • 3

    盒子是可重用的,因为T可以用任何东西来代替

    (5)只读数组类型

    ReadnonlyArray是一个特殊的类型,描述了不应该被改变的数组

    (6)元组类型

    Tuple 类型是另一种 Array 类型,它确切地知道包含多少个元素,以及它在特定位置包含哪些类型,同样可以用?后缀标记可选元素,以及readonly标记只读属性

    type StringNumber = [string, number]
    
    • 1

    二、最佳实践

    1、通过index文件简化模块引用

    对于中等以上规模的应用,每个文件中的import语句和从其他文件中引用的模块数量会明显增多,因此,为了更方便地从外部引用某个目录内存在的文件,一般会创建一个index.ts的索引文件,在该文件中对模块进行再次导出,即执行Re-exports;

    在ESM与CommonJS中,当导入目标的路径以目录名为结尾时,会引用目标目录中的index.js或index.ts文件,可以像如下的方式根据索引文件中的描述清楚的引入模块;

    // Before 
    import { LogoComponent } from "./components/header/logo.component"; 
    import { NavigationComponent } from "./components/header/navigation.component"; 
    // After 
    import { LogoComponent, NavigationComponent } from "./components/header";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不仅路径的描述更加简洁,还可以使用一个import语句来引入多个模块,从而减少了文件中模块描述的数量。此外,添加禁止引用未在index.ts中Re-exports的文件的规则,可以减少模块相互之间的依赖与影响。

    2、使类型引用规范化

    由于TS中可以引用类型定义中的某些类型信息,因此在类型定义的上下文中,应尽可能的规范化使用类型引用;
    项目中数据模型一般使用类型别名定义时,若要为这个模型的属性或函数提供类型信息,需要引用模型的属性类型来而不是通过标准类型名称;

    type UserModel = { id: number;   name: string;   email: string; };  
    type Props = { name: UserModel["name"]; } // 不写`name: string;`
    
    • 1
    • 2

    这样做有两个好处:当类型信息变化时,影响范围是清晰明了的,且得益于类型检查;可以跳转到相关类型的代码。

    3、使用Object而非Enums

    尽管TS中存在Enums枚举类型,但其用例较少,完全可以被对象来替代;

    enum Status {   
        Published = "published",   
        Draft = "draft", 
    } 
    // 上面的String Enums可以使用Object来替代 
    const status = {   
        Published: "published",   
        Draft: "draft", 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用Object而非Enums的理由如下:

    • Enums中的值仅能使用数字和字符串类型,而Object则可以使用布尔值和通过Type Annotations组合出的联合类型;
    • 调用以Enums为参数的函数时,每次都需要用import引用该Enums;
    • 不管是传入的JSON对象还是用户输入的数据,使用Object都可以轻松的验证外部输入的值。由于无法在循环中依次引用每个Enums中的键值(无迭代器),因此如果要判断Enums成员中是否包含某个值,只能一个一个地去比较。

    4、使用Typescript或者框架内置的标准类型

    不管是TS语言还是React等框架,均提供了用于开发的标准类型,比如使用React时,@types/react提供了一些类型定义,可以使用其中的ComponentProps 类型从组件中提取其Props类型

    5、不使用Typescript独有的模块加载方式(TypeScript Modules)

    不管是工具库还是服务端的Node.js,现在开发Web前端应用的话基本上都是使用ESM的模块方案,CommonJS作为早期的模块管理方案诞生于Node.js中,被使用在服务端Node.js及工具库中,之后ECMAScript推出了ESM方案,未来ESM的使用应该会越来越广泛;
    ESM的Default Exports今后或许会成为事实标准,其定义为使用export default xxx导出的值是具有default属性的对象,并且使用import xxx from导入的值是引用导出值的default属性;

    6、第三方库的使用优先级

    库的选择是一个开发应用时的关键问题,一般都会参考GitHub的star数、npm下载量、社区活跃度、文档的丰富度等等。另外,使用TypeScript开发应用时,库是否是由TypeScript开发的也是重要的指标之一,建议在star等指标差不多的时候,按以下优先级:

    • 是使用TypeScript编写的库吗?
    • 库中是否包含类型定义文件?
    • 库是否通过@types/*提供类型定义?

    7、不要使用除了any外不安全的类型

    首先,尽量不要使用any来标记变量类型,其次,更不要除了any之外还其他不安全的类型,比如{},{}类型不仅与空对象是同种类型,同时也是与空值外的任何类型一致的低安全性类型。

  • 相关阅读:
    pta团队天题题-阅览室(c++)
    为什么停用CentOS?
    阿里P8架构师分享内部开源的JVM垃圾回收PDF文档,共23.3W字
    gitee git 打一个 tag
    Java学习笔记(十七)
    ESP8266智能家居(5)——开发APP深入篇
    【Spring】Spring常见面试题总结
    Centos 7.9 一键安装 Oracle 12CR2(240116)单机 PDB
    MyBatis中动态 SQL 语句-更复杂的查询业务需求
    Java导入Excel文档到数据库
  • 原文地址:https://blog.csdn.net/weixin_44942126/article/details/126645911