20分钟0基础速通TypeScript核心用法本文将引导读者快速掌握 TypeScript 的类型推断、注解、函数参数、
0 # 关于 TypeScript
我们知道JavaScript是一个弱类型的语言,因此在使用过程中是相当灵活的。但在大型项目开发中,这种灵活会导致代码中藏匿着许多不经意产生的类型问题导致代码不易维护——因为发现问题时已经是报错相见了。
TypeScript是由微软开发维护的JavaScript的超集,它在JavaScript的基础之上添加了静态类型的支持。这意味着TypeScript可以在编译时发现并报告类型错误,而不是在运行时才发现。
TypeScript 的优点
- 类型安全:TS的类型系统可以帮助开发者在编码阶段就发现并修复潜在的错误,提高代码的可靠性和稳定性。
- 更好的tooling支持:TS提供了更好的IDE支持,如代码补全、重构、跳转定义等功能,提高了开发效率。
- 更好的可维护性:TS的类型声明使得代码的结构和意图更加清晰,增强了可读性和可维护性,特别是在团队协作和大型项目中非常有优势。
1 # TypeScript 的使用
1. 在线学习 TypeScript
新手起步可以不在本地安装 TypeScript 的环境,而是到 官网的 Playground 页面 进行在线练习。
在线练习有两个好处:
- 不用安装,即开即用,懒人必备!
- 会提示报错,同时也会输出等效的 JavaScript 代码辅助理解!
2. 在项目中安装 TypeScript
1) 初始化 Node.js 项目(已有项目则跳过)
在项目目录中执行:npm init -y
2) 安装 TypeScript
在项目目录中执行:npm i typescript
3) 初始化 TypeScript
在项目目录中执行:npx tsc --init
,根目录会自动创建TS的编译配置的 tsconfig.json 文件。
tsconfig.json 文件包含以下ℹ配置信息:
- 目标 ECMAScript 版本
- 输出目录
- 模块系统
- ...
更多内容可以自己查阅文件中的ℹ注释哦~
4) 创建并编写 TypeScript 文件
在项目中创建 .ts
结尾的TypeScript文件。
5) 配置编译命令
- 可以在 package.json 的
scripts
部分添加一个编译命令,如"build": "tsc"
。也可以直接在项目目录中使用npx tsc
命令编译。 - 如设置了编译命令,之后在项目目录下运行
npm run build
就可以将项目中所有 TypeScript 代码编译成 JavaScript 代码,输出到 tsconfig.json 中outDir
属性指定的目录下(默认在项目根目录)。
⚠ 注意: TypeScript 文件不可以在 Node.js 或浏览器中直接运行,因此最后都需要通过编译转换为 JavaScript 文件才可以测试和上线。
3. VS Code 中编写 TypeScript
1) 安装 TypeScript 语言支持扩展
这款插件是微软开发的官方支持插件,用 VS Code 写 TypeScript 必装无疑!
2) 安装 TypeScript 报错优化扩展
💁 这是第三方作者开发的一款 TypeScript 的附属插件,可以优化TypeScript的报错提示(如下图)。


个人觉得还是非常好用的,而且开源,大家可以到 Github 支持这位作者!
3 # 类型限定
TypeScript 让 JavaScript 从弱类型变成了强类型,它的一个主要特点就是对变量和表达式进行类型限定。通过类型限定,编译器能够在代码执行前发现潜在的类型错误,从而提高代码的可靠性和可维护性。
TypeScript 有 3种 方式确定变量或表达式的类型:
- 类型推断
- 类型注解
- 类型断言
且变量的类型一旦被确定就不能再被更改。
1. 类型推断
TypeScript 拥有强大的类型推断能力,它可以根据代码的上下文自动推断出变量的类型。例如:
let message = "Hello, TypeScript!"; // TypeScript 推断出 message 是 string 类型
message = 10; // 报错:不能将类型“number”分配给类型“string”
在上面的例子中,TypeScript 会根据赋值语句自动推断出 message
变量的类型是 string
。如果我们之后试图将非字符串的值赋给 message
,编译器将会报错。
2. 类型注解
虽然 TypeScript 能够进行类型推断,但这并不直观,会影响对代码的阅读。而明确的类型注解可以提高代码的可读性和明确性。类型注解允许我们显式地声明变量的类型。例如:
let count: number;
count = 42; // 正确
count = "Hello"; // 错误:不能将类型“string”分配给类型“number”
类型注解的基本格式就是在变量名后添加 : 类型名
。
通过类型注解,我们可以清晰地定义 count
变量的类型为 number
,并在编译时捕获类型不匹配的错误。
3. 类型断言
在某些情况下,我们可能已经知道某个值的确切类型,但 TypeScript 编译器无法通过类型推断得出。此时,可以使用类型断言告诉编译器我们对类型的假设。
类型断言有两种语法:
let someValue: any = "this is a string";
- 方式1:变量名后加
as 类型名
let strLength: number = (someValue as string).length;
- 方式2:变量名前加
<类型名>
let strLength: number = (<string>someValue).length;
在上面的例子中,someValue
被声明为 any
类型,通过类型断言,我们明确地告诉编译器 someValue
是 string
类型,并获取它的长度。
类型断言确实有一些使用场景,但是并不是必须的。就像上面的例子中,即使没有类型断言,编译器也不会有任何报错。
但是使用类型断言可以让 TypeScript 更好地理解你的代码意图,它的常用用途一般有以下 2 种:
-
处理
any
类型 当一个变量被声明为any
类型时,TypeScript 无法对它进行类型检查。此时使用类型断言可以告诉编译器这个变量的实际类型,从而可以访问该类型的属性和方法。 -
处理联合类型
✋联合类型将会在 4 # 基础类型和联合类型 中介绍。建议阅读完全文后返回看此部分。
当一个变量是联合类型时,TypeScript 无法确定它的具体类型。使用类型断言可以将它转换成更具体的类型,从而可以访问该类型特有的属性和方法。
interface Circle { kind: 'circle'; radius: number; } interface Square { kind: 'square'; side: number; } type Shape = Circle | Square; function getArea(shape: Shape) { if ((shape as Circle).radius !== undefined) { return Math.PI * (shape as Circle).radius ** 2; } else { return (shape as Square).side * (shape as Square).side; } }
这里我们定义了一个联合类型
Shape
,它可以是Circle
或Square
。在getArea
函数中,我们使用类型断言将shape
参数转换为具体的Circle
或Square
类型,从而可以访问对应的属性。
⚠ 使用类型断言时要谨慎,因为错误的断言可能会导致☠运行时错误。
✔ 类型断言仅在我们非常确定变量的实际类型时使用。
4 # 基础类型和联合类型
1. 基础类型
前面提到 TS 可以使用类型注解的方式声明变量类型,这些 JS 的基础类型都可以声明:
string
number
boolean
null
undefined
当然也有比较特殊的 any
代表所有类型都可以。
2. 联合类型
联合类型允许一个变量可以是多种类型之一。使用管道符 (|
) 分隔每个类型。例如:
let value: string | number;
value = "Hello"; // 正确
value = 123; // 也正确
value = true; // 错误:不能将类型“boolean”分配给类型“string | number”
联合类型常用于函数参数,以便允许多种输入类型。例如:
function printId(id: string | number) {
console.log("Your ID is: " + id);
}
printId("ABC123"); // 正确
printId(789); // 也正确
5 # 数组、元组与枚举
1. 数组
数组是存储同类型数据的集合。可以使用两种方式声明数组类型:
let numbers: number[] = [1, 2, 3, 4, 5];
let strings: Array<string> = ["one", "two", "three"];
如果需要数组存储多种类型数据,则可以使用联合类型,甚至简单粗暴用 any
:
let numbers: (number|string)[] = ["zero", 1, 2, 3, 4, 5];
let strings: Array<string|number> = ["one", "two", "three", 4];
let arr: any = ["one", "two", "three", 4, true, ];
2. 元组
元组(Tuple) 适用于固定长度和已知类型的数组。声明元组时,需要指定每个元素的类型。例如:
let person: [string, number] = ["John", 30];
person = ["John", "thirty"]; // 报错:不能将类型"[string,string]"分配给类型"[string,number]"
person = ["John"]; // 报错:不能将类型"[string]"分配给类型"[string,number]”。源具有1个元素,但目标需要2个
3. 枚举
枚举(Enum) 可以用来限定可选值,或者让数值更具语义化(如返回编码等)。
使用枚举可以使代码更具可读性。声明枚举时可以使用 enum
关键字。例如:
enum Direction {
Up,
Down,
Left,
Right
}
let move: Direction = Direction.Up;
👌 枚举类型是一个类数组对象,因此也可以用数组的方式进行访问。
console.log(Direction[0]); // Up
⚠ 但注意如用数组的方式访问,其类型不是枚举类型而是值原本的类型。
let move: Direction = Direction[0]; // 报错:不能将类型"string"分配给类型"Direction
也可以为枚举成员指定具体的值,也就是让数值语义化:
enum Status {
Success = 200,
NotFound = 404,
ServerError = 500
}
let response: Status = Status.Success;
枚举使代码更具可读性和可维护性,特别是在处理常量值时。
6 # 函数
1. 参数、可选参数、剩余参数
函数的参数可以包含必需参数、可选参数和剩余参数三个部分。
-
必需参数:默认情况下,TypeScript 中的函数参数都是必需的。例如:
function add(x: number, y: number): number { return x + y; } add(1); // 报错:应当有2个参数,但只有一个
-
可选参数:在参数名的末尾使用
?
标记,则可设置为可选参数。例如:⚠ 可选参数必须在所有必需参数的右侧,不可以交叉摆放。
function greet(name: string, greeting?: string): string { return greeting ? `${greeting}, ${name}` : `Hello, ${name}`; } console.log(greet('John', 'Good morning')); // Good morning, John console.log(greet('John')); // Hello, John
-
剩余参数:在最后一个参数的参数名前使用
...
以定义不定数量的参数。例如:⚠ 剩余参数必须指定数组类型,剩余的多个参数都会保存在一个数组中。
function sum(...numbers: number[]): number { return numbers.reduce((acc, curr) => acc + curr, 0); }
2. 返回值
函数的返回值类型可以显式声明在参数()
的后面,或者不声明(默认any
)。
如果确定函数没有返回值,则可以使用 void
类型限定。例如:
function sayHello(): void {
console.log("Hello!");
}
function multiply(a: number, b: number): number {
return a * b;
}
7 # 接口
接口(Interface) 是一种用于定义对象结构的方式,类似于结构体。它描述了对象的属性和方法,但不包含具体的实现。接口的主要作用是确保对象符合特定的形状。
可以使用 interface
关键字来定义接口。例如:
interface Person {
name: string;
age: number;
greet(): string;
}
let john: Person = {
name: "John",
age: 25,
greet: () => "Hello!"
};
接口还可以使用 extends
关键字 扩展(extend) 其他接口:
interface Employee extends Person {
employeeId: number;
}
let jane: Employee = {
name: "Jane",
age: 30,
employeeId: 123,
greet: () => "Hi!"
};
8 # 类型别名
类型别名(Type Alias) 使用 type
关键字定义,可以为任何类型创建一个新名称。类型别名可以用于基础类型、联合类型、元组等。例如:
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "Hello"; // 正确
value = 123; // 也正确
类型别名也可以用于对象类型,达成和接口类似的效果:
type Point = {
x: number;
y: number;
};
let point: Point = { x: 10, y: 20 };
⚠ 虽然
type
和interface
在定义对象类型时有很多相似之处,并且在大多数情况下可以互换使用,但它们在扩展方式、类型组合、复杂类型定义和声明合并等方面存在差异。还是更加建议使用接口的方式。
9 # 泛型
如果我们想有一个函数的参数可以接受多种类型,但多个参数必须是同一类型。怎么实现?
function test(arg1: string|number, arg2: string|number) {...}
上面这个例子就无法实现,因为arg1
与arg2
可能存在交叉使用的情况,不能保证参数的类型相同。
而泛型(Generics) 允许定义函数、接口或类时使用类型参数,使其能够处理多种类型。
常用的泛型定义使用尖括号 <>
语法。例如:
function identity<T>(arg1: T, arg2: T): T {
return arg;
}
let output1 = identity<string>("Hello", "world!");
let output2 = identity<number>(123, 321);
⚠ 在调用含泛型的函数时,可以不用
<>
指定泛型,TS 会进行智能推断。但为了代码更加清晰直观,还是建议用<>
指明泛型类型。
不仅在函数中,泛型也可以应用于接口和类:
interface Box<T> {
contents: T;
}
let stringBox: Box<string> = { contents: "Hello" };
let numberBox: Box<number> = { contents: 123 };
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;
END # 总结快查
类型限定
TypeScript 提供三种方式确定变量或表达式的类型,以提高代码的可靠性和可维护性:
-
类型推断:自动推断变量类型,无需显式声明。适用于简单情况。
- 示例:
let message = "Hello, TypeScript!";
- 示例:
-
类型注解:显式声明变量类型,增强可读性。
- 使用场景:复杂代码、团队协作。
- 关键字:
let count: number;
-
类型断言:手动指定类型,适用于编译器无法推断类型的情况。
- 使用场景:处理
any
类型、联合类型。 - 关键字:
let strLength: number = (someValue as string).length;
- 使用场景:处理
基础类型和联合类型
-
基础类型:包括
string
、number
、boolean
、null
和undefined
,用于声明变量的具体类型。- 使用场景:明确变量类型,防止类型错误。
- 关键字:
let username: string;
-
联合类型:允许变量可以是多种类型之一,使用
|
分隔。- 使用场景:函数参数允许多种类型输入。
- 关键字:
let value: string | number;
数组、元组与枚举
-
数组:存储同类型数据的集合。
- 使用场景:处理一组同类型数据。
- 关键字:
let numbers: number[];
-
元组:用于固定长度和已知类型的数组。
- 使用场景:定义固定结构的数据。
- 关键字:
let person: [string, number];
-
枚举:为数值类型提供友好名称,增强代码可读性。
- 使用场景:定义一组相关常量。
- 关键字:
enum Direction { Up, Down, Left, Right }
函数
-
参数、可选参数、剩余参数:定义函数的输入,支持必需参数、可选参数和剩余参数。
- 使用场景:函数参数管理。
- 关键字:
function greet(name: string, greeting?: string): string {}
-
返回值:显式声明函数的返回值类型。
- 使用场景:确保函数返回值类型正确。
- 关键字:
function multiply(a: number, b: number): number {}
接口
接口(Interface) 用于定义对象的结构,包括属性和方法。接口确保对象符合特定的形状。
- 使用场景:定义对象结构,确保类型安全。
- 关键字:
interface Person { name: string; age: number; greet(): string; }
- 扩展接口:
interface Employee extends Person { employeeId: number; }
类型别名
类型别名(Type Alias) 使用 type
关键字,为任意类型创建新名称。
- 使用场景:为复杂类型创建简洁名称。
- 关键字:
type StringOrNumber = string | number;
- 定义对象类型:
type Point = { x: number; y: number; };
建议优先使用接口来定义对象类型,因其扩展性更强。
泛型
泛型(Generics) 允许定义函数、接口或类时使用类型参数,使其能够处理多种类型。
- 使用场景:定义可重用、灵活和类型安全的代码。
- 关键字:
function identity<T>(arg: T): T { return arg; }
- 泛型接口和类:
interface Box<T> { contents: T; }
、class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; }
转载自:https://juejin.cn/post/7379060488351989770