TypeScript 教程:从基础到进阶
TypeScript 是由 Microsoft 开发和维护的一种开源编程语言。它是 JavaScript 的一个超集,意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。TypeScript 的主要目的是为 JavaScript 添加静态类型定义,从而在开发大型应用时提供更强的可维护性、可读性和更少的运行时错误。
I. 介绍与基础
1. 什么是 TypeScript?
- 定义:TypeScript 是 JavaScript 的一个超集,它扩展了 JavaScript 的功能,最显著的特点是引入了静态类型。
- 核心特性:
- 静态类型:允许你在开发阶段定义变量、函数参数和返回值的类型。
- 更好的工具支持:得益于类型信息,IDE 可以提供更智能的代码补全、错误检查和重构功能。
- 可伸缩性:类型系统使得大型项目的代码结构更清晰,更易于理解和维护。
- 优势:
- 早期错误检测:在编译阶段而非运行时发现类型相关的错误。
- 增强可读性:类型定义让代码意图更明确。
- 提高开发效率:智能感知和自动补全减少了查找文档和手动纠错的时间。
- 更好的协作:团队成员更容易理解和修改彼此的代码。
- 与 JavaScript 的区别:JavaScript 是动态类型语言(运行时确定类型),而 TypeScript 是静态类型语言(编译时确定类型)。
- TypeScript 编译器 (TSC):TypeScript 代码不能直接在浏览器或 Node.js 中运行。它需要通过 TypeScript 编译器(
tsc)编译成纯 JavaScript 代码。
2. 设置开发环境
- 安装 Node.js 和 npm:TypeScript 编译和管理通常依赖于 Node.js 和其包管理器 npm(或 yarn)。访问 Node.js 官网 下载并安装。
- 安装 TypeScript:
bash
npm install -g typescript # 全局安装
# 或者在项目内安装
npm install --save-dev typescript - 初始化项目:
bash
mkdir my-ts-project
cd my-ts-project
npm init -y
npx tsc --init # 创建 tsconfig.json 文件 tsconfig.json配置:这是 TypeScript 项目的核心配置文件,用于指导编译器如何将 TypeScript 代码编译成 JavaScript。"target": 指定编译后的 JavaScript 版本 (如 “es5”, “es2016”, “es2020”)。"module": 指定模块系统 (如 “commonjs”, “esnext”)。"outDir": 指定编译输出目录。"strict": 启用所有严格类型检查选项 (强烈推荐)。"rootDir": 指定 TypeScript 文件的根目录。
- 代码编辑器:推荐使用 VS Code,它对 TypeScript 有出色的内置支持,包括语法高亮、智能补全和错误提示。
3. 你的第一个 TypeScript 程序
-
创建
.ts文件:在my-ts-project目录下创建一个app.ts文件。
``typescriptHello, ${name}!`;
// app.ts
function greet(name: string) {
return
}let user = “TypeScript”;
console.log(greet(user));
2. **编译和运行**:bash
npx tsc app.ts # 编译 app.ts 生成 app.js
node app.js # 运行编译后的 JavaScript 文件
如果你配置了 `tsconfig.json` 并希望编译整个项目:bash
npx tsc # 编译器会查找 tsconfig.json 并编译所有相关文件
node dist/app.js # 假设 outDir 配置为 dist
“`
II. TypeScript 基础
1. 基本类型
TypeScript 提供了以下内置类型:
string:表示文本数据。
typescript
let username: string = "Alice";number:表示浮点数和整数。
typescript
let age: number = 30;boolean:表示真/假值。
typescript
let isActive: boolean = true;- 类型推断:TypeScript 能够根据变量的初始值自动推断其类型。
typescript
let message = "Hello"; // message 会被推断为 string 类型 any:表示任意类型,可以接受任何值。应尽量避免使用any,因为它会丧失 TypeScript 的类型检查优势。
typescript
let data: any = 10;
data = "hello";
data = true;void:通常用于表示函数没有返回值。
typescript
function warnUser(): void {
console.log("This is a warning.");
}undefined和null:它们各自是自己的类型,并且在strictNullChecks关闭时,可以赋值给其他类型。
typescript
let u: undefined = undefined;
let n: null = null;never:表示永远不会有值的类型,通常用于抛出异常或无限循环的函数返回值类型。
typescript
function error(message: string): never {
throw new Error(message);
}
2. 数组和元组
- 数组:表示同类型元素的集合。
typescript
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"]; - 元组 (Tuple):表示已知元素数量和类型的数组,各元素的类型不必相同。
typescript
let person: [string, number] = ["Alice", 30];
// person = [30, "Alice"]; // Error: Type 'number' is not assignable to type 'string'
3. 函数
-
函数类型注解:为函数参数和返回值添加类型。
“`typescript
function add(x: number, y: number): number {
return x + y;
}let sum = add(5, 3); // sum 的类型会被推断为 number
* **可选参数**:使用 `?` 标记参数为可选。可选参数必须在必选参数之后。typescript
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + ” ” + lastName;
} else {
return firstName;
}
}
* **默认参数**:为参数提供默认值。typescript
function sayHello(name: string = “World”): string {
returnHello, ${name}!;
}
* **剩余参数**:使用 `...` 将多个参数收集到一个数组中。typescript
function sumAll(firstNum: number, …restOfNumbers: number[]): number {
return firstNum + restOfNumbers.reduce((acc, num) => acc + num, 0);
}
* **函数重载**:允许为同一个函数提供多个函数签名,以处理不同类型的输入。typescript
function overloadExample(x: string): string;
function overloadExample(x: number): number;
function overloadExample(x: string | number): string | number {
if (typeof x === ‘string’) {
return x.toUpperCase();
}
return x * 2;
}
* **箭头函数**:与 ES6 相同,TypeScript 支持箭头函数,并且可以为它们添加类型。typescript
let multiply = (a: number, b: number): number => a * b;
“`
4. 对象
- 对象类型定义:使用
{}定义对象的结构和属性类型。
typescript
let user: { name: string; age: number } = {
name: "Bob",
age: 25
}; - 可选属性:使用
?标记对象的属性为可选。
typescript
let car: { brand: string; model: string; year?: number } = {
brand: "Tesla",
model: "Model 3"
};
III. TypeScript 核心技能 (中级)
1. 接口 (Interfaces)
接口用于定义对象的结构、函数签名或类的契约。它们是 TypeScript 中最强大的类型检查机制之一。
“`typescript
// 定义一个接口
interface Person {
readonly id: number; // 只读属性
name: string;
age?: number; // 可选属性
greet(message: string): void; // 函数签名
}
// 实现接口的对象
let john: Person = {
id: 1,
name: “John Doe”,
greet(message: string) {
console.log(${this.name} says: ${message});
}
};
john.greet(“Hello!”);
// john.id = 2; // Error: Cannot assign to ‘id’ because it is a read-only property.
// 接口继承
interface Employee extends Person {
employeeId: string;
}
let jane: Employee = {
id: 2,
name: “Jane Smith”,
employeeId: “EMP001”,
greet(message: string) {
console.log(Employee ${this.name} says: ${message});
}
};
“`
2. 类型别名 (Type Aliases)
类型别名允许你为任何类型定义一个新名称,包括基本类型、联合类型、元组等。
“`typescript
type ID = string | number; // 为联合类型定义别名
type Point = { x: number; y: number }; // 为对象类型定义别名
let userId: ID = “abc-123”;
let userPoint: Point = { x: 10, y: 20 };
// 类型别名与接口的区别:
// 接口只能用于定义对象类型、函数类型。可以实现继承和合并。
// 类型别名可以用于定义任何类型,但不能被实现或合并。
“`
3. 联合类型 (Union) 和交叉类型 (Intersection)
- 联合类型 (
|):表示一个值可以是多种类型之一。
typescript
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
printId(101);
printId("202"); -
交叉类型 (
&):将多个类型合并为一个类型,新类型将具有所有合并类型的特性。
“`typescript
interface Draggable {
drag(): void;
}interface Resizable {
resize(): void;
}type DraggableResizable = Draggable & Resizable;
let uiElement: DraggableResizable = {
drag() { console.log(“Dragging!”); },
resize() { console.log(“Resizing!”); }
};
“`
4. 枚举 (Enums)
枚举允许你定义一组命名的常量。
“`typescript
// 数字枚举 (默认从 0 开始)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
let go: Direction = Direction.Up;
console.log(go); // 0
// 自定义起始值
enum StatusCode {
NotFound = 404,
Success = 200,
Accepted = 202
}
console.log(StatusCode.NotFound); // 404
// 字符串枚举
enum LogLevel {
ERROR = “ERROR”,
WARN = “WARN”,
INFO = “INFO”,
DEBUG = “DEBUG”
}
console.log(LogLevel.ERROR); // “ERROR”
// 常量枚举 (编译后不会生成实际对象,直接替换为值)
const enum Color {
Red, Green, Blue
}
let c: Color = Color.Green;
“`
5. 类 (Classes)
TypeScript 对 ES6 的类进行了扩展,增加了类型注解、访问修饰符和接口实现等特性。
“`typescript
class Animal {
// 访问修饰符: public (默认), private, protected
private name: string; // 只能在类内部访问
protected species: string; // 只能在类内部和子类中访问
constructor(theName: string, theSpecies: string) {
this.name = theName;
this.species = theSpecies;
}
public move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name, “Canine”); // 调用父类构造函数
}
bark() {
console.log('Woof! My species is ' + this.species); // 可以访问 protected 成员
}
}
let dog = new Dog(“Buddy”);
dog.bark();
dog.move(10);
// console.log(dog.name); // Error: Property ‘name’ is private.
// 实现接口
interface Greetable {
greeting: string;
greet(): void;
}
class Greeter implements Greetable {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
console.log(“Hello, ” + this.greeting);
}
}
// 抽象类:不能直接实例化,只能被继承
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log(“Department name: ” + this.name);
}
abstract printMeeting(): void; // 抽象方法必须在子类中实现
}
class AccountingDepartment extends Department {
constructor() {
super(“Accounting and Auditing”); // 构造函数中必须调用 super()
}
printMeeting(): void {
console.log(“The Accounting Department meets each Monday at 10am.”);
}
generateReports(): void {
console.log(“Generating accounting reports…”);
}
}
“`
6. 类型断言 (Type Assertions) 和类型守卫 (Type Guards)
- 类型断言:告诉编译器你比它更了解某个值的类型。
- “as” 语法 (推荐):
typescript
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; - 尖括号语法 (不推荐与 JSX 混淆):
typescript
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
- “as” 语法 (推荐):
- 类型守卫:用于在运行时判断变量的类型,并在此特定作用域内确保其类型。
typeof守卫:
typescript
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}-
instanceof守卫:
“`typescript
class Fish { swim() {} }
class Bird { fly() {} }function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}function getSmallPet(): Fish | Bird {
return Math.random() > 0.5 ? new Fish() : new Bird();
}let pet = getSmallPet();
if (isFish(pet)) {
pet.swim(); // pet 在此作用域内被推断为 Fish 类型
} else {
pet.fly(); // pet 在此作用域内被推断为 Bird 类型
}
``parameterName is Type` 的函数来定义。
* **用户自定义类型守卫**:通过返回
IV. 高级 TypeScript 概念
1. 泛型 (Generics)
泛型允许你编写可以适用于多种类型的组件,同时保持类型安全。它们在函数、接口和类中都很有用。
“`typescript
// 泛型函数
function identity
return arg;
}
let output1 = identity
let output2 = identity(123); // 类型推断,T 为 number
// 泛型接口
interface GenericIdentityFn
(arg: T): T;
}
let myIdentity: GenericIdentityFn
// 泛型类
class GenericNumber
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zero: T, adder: (x: T, y: T) => T) {
this.zeroValue = zero;
this.add = adder;
}
}
let myGenericNumber = new GenericNumber
console.log(myGenericNumber.add(5, 10)); // 15
// 泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity
console.log(arg.length); // 现在我们可以访问 .length 属性
return arg;
}
loggingIdentity(“hello”); // length: 5
loggingIdentity([1, 2, 3]); // length: 3
// loggingIdentity(3); // Error: Argument of type ‘number’ is not assignable to parameter of type ‘Lengthwise’.
// keyof 类型操作符
// keyof 操作符接受一个对象类型,生成其键的字符串或字符串字面量联合类型。
type PointKeys = keyof Point; // “x” | “y”
function getProperty
return obj[key];
}
let p = { x: 10, y: 20 };
let xValue = getProperty(p, “x”); // xValue 的类型是 number
“`
2. 内置工具类型 (Utility Types)
TypeScript 提供了一系列内置工具类型,用于常见的类型转换操作。
Partial<T>: 将T的所有属性变为可选。Required<T>: 将T的所有属性变为必选。Readonly<T>: 将T的所有属性变为只读。Pick<T, K>: 从T中选择K中列出的属性。Omit<T, K>: 从T中删除K中列出的属性。Exclude<T, U>: 从T中排除可分配给U的类型。Extract<T, U>: 从T中提取可分配给U的类型。NonNullable<T>: 从T中排除null和undefined。Record<K, T>: 创建一个对象类型,其属性键为K类型,属性值为T类型。ReturnType<T>: 获取函数类型T的返回值类型。Parameters<T>: 获取函数类型T的参数类型(元组形式)。
“`typescript
interface Todo {
title: string;
description: string;
completed: boolean;
}
type PartialTodo = Partial
// { title?: string; description?: string; completed?: boolean; }
type ReadonlyTodo = Readonly
// { readonly title: string; readonly description: string; readonly completed: boolean; }
type TodoPreview = Pick
// { title: string; completed: boolean; }
type TodoWithoutDescription = Omit
// { title: string; completed: boolean; }
function createLogger(): (message: string) => void {
return (msg: string) => console.log(msg);
}
type LoggerFn = ReturnType
“`
3. 装饰器 (Decorators)
装饰器是一种特殊类型的声明,它能够附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 这种形式。
注意:装饰器目前仍是实验性特性,需要在 tsconfig.json 中启用 "experimentalDecorators": true 和 "emitDecoratorMetadata": true。
``typescriptCalling ${propertyKey} with arguments: ${JSON.stringify(args)}
// 方法装饰器
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log();${propertyKey} returned: ${JSON.stringify(result)}`);
const result = originalMethod.apply(this, args);
console.log(
return result;
};
return descriptor;
}
class Calculator {
@LogMethod
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// Output:
// Calling add with arguments: [2,3]
// add returned: 5
“`
4. 模块 (Modules) 和命名空间 (Namespaces)
- 模块 (Modules):在现代 TypeScript/JavaScript 开发中,模块是组织代码的主要方式。每个文件都是一个模块,通过
import和export关键字进行导入导出。src/utils.ts
typescript
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export const PI = 3.14159;src/app.ts
typescript
import { capitalize, PI } from './utils';
console.log(capitalize("hello")); // Hello
console.log(PI); // 3.14159
-
命名空间 (Namespaces):命名空间(前身为内部模块)是早期 TypeScript 中用于组织代码的方式,主要用于避免全局命名冲突。在现代应用中,通常推荐使用 ES 模块。命名空间主要用于大型全局库或旧代码库。
“`typescript
namespace MyValidation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } }}
let validator = new MyValidation.LettersOnlyValidator();
console.log(validator.isAcceptable(“abc”)); // true
“`
5. 高级类型操作
-
条件类型 (Conditional Types):基于条件表达式的类型推断。
“`typescript
type TypeName=
T extends string ? “string” :
T extends number ? “number” :
T extends boolean ? “boolean” :
T extends undefined ? “undefined” :
T extends Function ? “function” :
“object”;type T1 = TypeName
; // “string”
type T2 = TypeName; // “object” // infer 关键字: 在条件类型中用于推断类型参数。
type UnpackPromise= T extends Promise ? U : T;
type A = UnpackPromise>; // string
type B = UnpackPromise; // string[]
* **映射类型 (Mapped Types)**:根据旧类型创建新类型,通常用于将旧类型的所有属性映射为新类型。typescript
type Keys = “option1” | “option2”;
type Flags = { [K in Keys]: boolean };
// { option1: boolean; option2: boolean; }
``keyof
结合和typeof` 可以创建非常灵活的类型。
6. 声明文件 (.d.ts)
当使用纯 JavaScript 编写的第三方库时,TypeScript 无法直接获取其类型信息。声明文件(.d.ts)提供了这些外部库的类型定义,让 TypeScript 编译器能够理解它们,从而在 TypeScript 项目中安全地使用这些库。
- 环境声明:对于全局变量或没有模块导出的库。
typescript
// my-lib.d.ts
declare var MY_GLOBAL_VAR: string;
declare function myFunction(x: number): number; - 模块声明:为没有自带类型定义的 JavaScript 模块创建声明。
typescript
// types/some-module/index.d.ts
declare module "some-module" {
export function doSomething(arg: string): void;
export class MyClass {
constructor(name: string);
getName(): string;
}
} - DefinitelyTyped:最大的高质量 TypeScript 声明文件仓库,通过
npm install @types/library-name安装。
V. 实际应用与最佳实践
1. 与框架/库集成
TypeScript 与现代前端和后端框架配合得非常好:
- React:通过
@types/react和@types/react-dom,可以为组件的 props、state、hooks 和事件处理函数提供类型安全。
typescript
interface MyComponentProps {
message: string;
count: number;
}
const MyComponent: React.FC<MyComponentProps> = ({ message, count }) => {
return <div>{message}: {count}</div>;
}; - Node.js/Express:通过
@types/node和@types/express,为路由处理器、请求/响应对象等提供类型。
typescript
import express, { Request, Response } from 'express';
const app = express();
app.get('/', (req: Request, res: Response) => {
res.send('Hello World!');
}); - Angular:Angular 是用 TypeScript 构建的,原生支持 TypeScript。
- Vue.js:Vue 3 原生支持 TypeScript,提供 Composition API 更好地利用类型推断。
2. 项目结构和工具
- 组织 TypeScript 项目:通常将 TypeScript 源文件放在
src/目录下,编译后的 JavaScript 和声明文件放在dist/或build/目录下。 - Linting:使用 ESLint 结合
@typescript-eslint/parser和@typescript-eslint/eslint-plugin来执行代码风格和潜在错误的检查。 - 构建工具:
- Webpack/Rollup/Vite.js:与 TypeScript 插件(如
ts-loader或rollup-plugin-typescript2)结合,将 TypeScript 文件打包成可在浏览器或 Node.js 中运行的 JavaScript。 tsc命令行工具:对于简单的项目或库,直接使用tsc进行编译。
- Webpack/Rollup/Vite.js:与 TypeScript 插件(如
- 测试 TypeScript 代码:
- Jest:结合
ts-jest或直接使用babel-jest,可以测试 TypeScript 代码。 - Mocha/Chai:结合
ts-node或预编译,也可以用于测试。
- Jest:结合
3. 最佳实践
- 启用严格模式:在
tsconfig.json中设置"strict": true,这会启用所有严格的类型检查选项,有助于编写更健壮的代码。 - 避免
any类型:尽量减少any的使用,只在确实无法推断类型或需要与非 TypeScript 代码交互时使用。 - 编写可维护和可伸缩的 TypeScript 代码:
- 清晰的接口和类型定义。
- 利用泛型实现代码重用。
- 合理组织模块和文件。
- 为复杂的类型或函数添加 JSDoc 注释。
- 将 JavaScript 项目迁移到 TypeScript:
- 逐步引入:可以从更改文件扩展名 (
.js到.ts或.jsx到.tsx) 开始,TypeScript 会在非严格模式下推断类型。 - 添加
tsconfig.json。 - 逐步添加类型注解,并启用更严格的类型检查选项。
- 逐步引入:可以从更改文件扩展名 (
4. 持续学习资源
- TypeScript 官方手册:https://www.typescriptlang.org/docs/handbook/intro.html
- TypeScript Playground:https://www.typescriptlang.org/play (在线尝试 TypeScript 代码)
- Type Challenges:https://github.com/type-challenges/type-challenges (通过解决类型难题来提高类型技能)
- 关注 TypeScript 社区和博客,了解最新特性和最佳实践。
希望这篇详细的 TypeScript 教程能帮助你从基础入门到掌握更高级的概念和实践!