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

一、常用知识点

1、概述

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

npm install -g typescript tsc hello.ts

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

function sayHello(person: string) {     
    return 'Hello, ' + person; 
}  
let user = 'Tom'; 
console.log(sayHello(user));

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;
}

函数的另一种表达方式

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

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

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

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

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

type ID = number | string

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

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

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

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

值 as 类型

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

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

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

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

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

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

3、类型缩小

(1)typeof

typeof的返回值:

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

在 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         
       }
}

(2)真值缩小

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

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

其他都会被强制转为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);
	}  
}

(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(); 
}

(5)instanceof

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

function logValue(x: Date | string) {     
    if (x instanceof Date) {         
        console.log(x.toUTCString());     
    } else {         
        console.log(x.toUpperCase());     
    } 
}

4、函数

(1)函数类型表达式

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

fn: (a: sting) => void

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

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

(2)调用签名

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

type fun = {     
	property: string;
	(arg: number) : boolean; 
}

(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)

(4)泛型函数

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

function fun<T>(arr: T[]): T | undefined {     return arr[0] }

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

function longest<Type extends { length: number }>(a: Type, b: Type) {   
    if (a.length >= b.length) {    
        return a;   
     } else {     
         return b; 
     } 
 }

(5)可选参数

通过?标记可选参数

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

(6)函数重载

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

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

5、对象类型

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

(1)属性

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

interface SomeType { 
	readonlt prop : string;
	a?: number 
}

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

interface StringArray { 
	[index: number]: string 
}

(2)扩展签名

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

(3)交叉类型

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

interface Colorful {
    color: string 
} 
interface: Circle {
    radius: number 
}  
type ColorfulCircle = colorful & Circle

(4)泛型对象类型

interface Box<T> {
    contents: T 
}

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

(5)只读数组类型

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

(6)元组类型

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

type StringNumber = [string, number]

二、最佳实践

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";

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

2、使类型引用规范化

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

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

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

3、使用Object而非Enums

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

enum Status {   
    Published = "published",   
    Draft = "draft", 
} 
// 上面的String Enums可以使用Object来替代 
const status = {   
    Published: "published",   
    Draft: "draft", 
};

使用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之外还其他不安全的类型,比如{},{}类型不仅与空对象是同种类型,同时也是与空值外的任何类型一致的低安全性类型。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值