TypeScript从入门到精通完全指南:类型系统、接口与泛型实战教程

TypeScript从入门到精通完全指南:类型系统、接口与泛型实战教程

TypeScript是JavaScript的超集,添加了可选的静态类型和基于类的面向对象编程。由微软开发并维护,TypeScript为大型应用的开发提供了强大的类型检查和代码提示能力。本文将从基础概念到高级特性,全面介绍TypeScript的核心技术。

一、TypeScript基础类型系统

1.1 基本数据类型

TypeScript提供了丰富的基本类型,帮助开发者在编译阶段捕获潜在的错误。

字符串类型:

1
2
3
4
5
6
7
// 字符串定义
let name: string = "Gene";
let greeting: string = `Hello, ${name}`;

// 多行字符串
let sentence: string = `Hello, my name is ${name}.
I'll be learning TypeScript today.`;

数字与布尔类型:

1
2
3
4
5
6
let age: number = 25;
let isActive: boolean = true;
let price: number = 19.99;
let hex: number = 0xff; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制

数组类型:

1
2
3
4
5
6
7
8
9
// 两种方式定义数组
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// 字符串数组
let names: string[] = ["Alice", "Bob", "Charlie"];

// 联合类型数组
let mixed: (string | number)[] = ["a", 1, "b", 2];

1.2 特殊类型详解

元组(Tuple):

元组允许定义具有固定数量元素且类型已知的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 声明元组类型
let person: [string, number];

// 正确初始化
person = ["Alice", 25];

// 错误示例
// person = [25, "Alice"]; // Error: 类型不匹配

// 访问元组元素
console.log(person[0].substr(1)); // OK, string类型
// console.log(person[1].substr(1)); // Error, number类型没有substr方法

// 解构元组
let [userName, userAge] = person;

枚举(Enum):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 数字枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green; // 值为1

// 指定起始值
enum Color2 {Red = 1, Green, Blue}
let c2: Color2 = Color2.Green; // 值为2

// 全部手动赋值
enum Status {
Pending = 0,
Approved = 200,
Rejected = 400
}

// 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}

// 反向映射(仅数字枚举)
let colorName: string = Color[1]; // "Green"

Any与Unknown:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Any类型:允许任何操作
let notSure: any = 4;
notSure = "maybe a string"; // OK
notSure = false; // OK
notSure.ifItExists(); // OK,运行时可能出错
notSure.toFixed(); // OK

// Unknown类型:类型安全的any
let uncertain: unknown = 4;
// uncertain.toFixed(); // Error: 需要先进行类型检查

// 类型断言后才能使用
if (typeof uncertain === "number") {
uncertain.toFixed(); // OK
}

Void、Null和Undefined:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Void:表示没有返回值
function warnUser(): void {
console.log("This is my warning message");
}

// 只能赋值null或undefined
let unusable: void = undefined;

// Null和Undefined
let u: undefined = undefined;
let n: null = null;

// 严格模式下null不能赋值给其他类型
// let num: number = null; // Error

Never类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 表示永不存在的值的类型
function error(message: string): never {
throw new Error(message);
}

// 推断返回类型为never
function fail() {
return error("Something failed");
}

// 无限循环
function infiniteLoop(): never {
while (true) {}
}

1.3 类型断言

类型断言告诉编译器”相信我,我知道自己在做什么”。

1
2
3
4
5
6
7
8
9
10
let someValue: any = "this is a string";

// 尖括号语法
let strLength1: number = (<string>someValue).length;

// as语法(推荐,JSX中也能使用)
let strLength2: number = (someValue as string).length;

// 双重断言(谨慎使用)
let strLength3: number = (someValue as any) as number;

二、接口与类型别名

2.1 接口基础

接口是TypeScript中定义对象类型的核心方式。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义接口
interface LabelledValue {
label: string;
}

// 使用接口
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj); // OK,TS会检查必需属性是否存在

可选属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface SquareConfig {
color?: string; // 可选属性
width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}

let mySquare = createSquare({color: "black"});

只读属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Point {
readonly x: number;
readonly y: number;
}

let p1: Point = {x: 10, y: 20};
// p1.x = 5; // Error: 只读属性不能修改

// 只读数组
let arr: ReadonlyArray<number> = [1, 2, 3];
// arr[0] = 12; // Error
// arr.push(5); // Error
// arr = arr.concat([4]); // Error

2.2 函数类型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义函数接口
interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
};

// 参数名可以不同
let mySearch2: SearchFunc = function(source: string, substring: string) {
return source.search(substring) > -1;
};

2.3 索引签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 字符串索引
interface StringArray {
[index: number]: string;
}

let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

// 可索引类型
interface NumberDictionary {
[index: string]: number;
length: number; // OK, length是number
// name: string; // Error, 与索引签名冲突
}

// 只读索引
interface ReadonlyStringArray {
readonly [index: number]: string;
}

2.4 类实现接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}

class Clock implements ClockInterface {
currentTime: Date = new Date();

setTime(d: Date) {
this.currentTime = d;
}

constructor(h: number, m: number) {}
}

// 接口继承
interface Shape {
color: string;
}

interface Square extends Shape {
sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;

2.5 类型别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用type创建类型别名
type StringOrNumber = string | number;
type Point = {x: number; y: number};

// 与接口的区别:可以定义原始类型、联合类型、元组
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

// 获取函数返回类型
type ReturnType = ReturnType<() => string>; // string

// 类型别名vs接口的主要区别
type Container<T> = {value: T}; // 可以定义泛型别名

三、泛型编程

3.1 泛型基础

泛型允许创建可重用的组件,支持多种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 无泛型版本
function identity(arg: any): any {
return arg;
}

// 泛型版本
function identity<T>(arg: T): T {
return arg;
}

// 调用方式
let output1 = identity<string>("myString");
let output2 = identity("myString"); // 类型推断

泛型约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 约束泛型必须具有length属性
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在知道有length属性
return arg;
}

// 可以使用
loggingIdentity("string"); // OK
loggingIdentity([1, 2, 3]); // OK
// loggingIdentity(3); // Error, number没有length

3.2 泛型接口与类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}

function identity2<T>(arg: T): T {
return arg;
}

let myIdentity: GenericIdentityFn<number> = identity2;

// 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

3.3 泛型约束与 keyof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // OK
// getProperty(x, "d"); // Error, "d"不是x的属性

// 在泛型中使用类类型
class BeeKeeper {
hasMask: boolean;
}

class ZooKeeper {
nametag: string;
}

class Animal {
numLegs: number;
}

class Bee extends Animal {
keeper: BeeKeeper;
}

class Lion extends Animal {
keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

四、高级类型

4.1 联合类型与交叉类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 联合类型
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}'.`);
}

// 交叉类型
interface ErrorHandling {
success: boolean;
error?: { message: string };
}

interface ArtworksData {
artworks: { title: string }[];
}

type ArtworksResponse = ArtworksData & ErrorHandling;

let response: ArtworksResponse = {
artworks: [{ title: "Mona Lisa" }],
success: true
};

4.2 类型守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// typeof类型守卫
function isNumber(x: any): x is number {
return typeof x === "number";
}

// instanceof类型守卫
class Bird {
fly() { console.log("flying"); }
}

class Fish {
swim() { console.log("swimming"); }
}

function move(pet: Bird | Fish) {
if (pet instanceof Bird) {
pet.fly();
} else {
pet.swim();
}
}

// in类型守卫
interface Cat {
meow(): void;
}

interface Dog {
bark(): void;
}

function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}

4.3 映射类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 内置映射类型
interface Person {
name: string;
age: number;
}

// Readonly
type ReadonlyPerson = Readonly<Person>;

// Partial
type PartialPerson = Partial<Person>;

// Pick
type NameOnly = Pick<Person, "name">;

// Record
type PageInfo = Record<string, {title: string}>;

// 自定义映射类型
type Nullable<T> = { [P in keyof T]: T[P] | null };
type Proxy<T> = { get(): T; set(value: T): void };
type Proxify<T> = { [P in keyof T]: Proxy<T[P]> };

4.4 条件类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 条件类型基础
type NonNullable<T> = T extends null | undefined ? never : T;

// 内置条件类型
// Exclude<T, U> - 从T中剔除可以赋值给U的类型
// Extract<T, U> - 提取T中可以赋值给U的类型
// NonNullable<T> - 从T中剔除null和undefined
// ReturnType<T> - 获取函数返回值类型
// InstanceType<T> - 获取构造函数类型的实例类型

// infer关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type ElementType<T> = T extends (infer E)[] ? E : T;

// 分布条件类型
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";

type T0 = TypeName<string>; // "string"
type T1 = TypeName<string[]>; // "object"
type T2 = TypeName<string | number>; // "string" | "number"

五、模块系统

5.1 模块导出

TypeScript 1.5之后,术语发生了变化:”内部模块”称为”命名空间”,”外部模块”简称为”模块”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 导出接口
export interface StringValidator {
isAcceptable(s: string): boolean;
}

// 导出常量
export const numberRegexp = /^[0-9]+$/;

// 导出类
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}

// 导出语句
class ZipCodeValidator2 implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator2 };

// 重命名导出
export { ZipCodeValidator2 as mainValidator };

// 默认导出
export default class DefaultValidator {
isAcceptable(s: string) {
return s.length > 0;
}
}

// export = 语法(CommonJS风格)
class MyValidator {
isAcceptable(s: string) {
return s.length > 0;
}
}
export = MyValidator;

5.2 模块导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 导入单个导出
import { ZipCodeValidator } from "./ZipCodeValidator";

// 重命名导入
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";

// 导入整个模块
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

// 导入默认导出
import DefaultValidator from "./DefaultValidator";

// 同时导入默认和命名导出
import DefaultValidator, { ZipCodeValidator } from "./Validators";

// 导入CommonJS模块
import zip = require("./ZipCodeValidator");
let validator2 = new zip();

// 仅导入类型
import type { SomeType } from "./someModule";

5.3 命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 定义命名空间
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}

const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;

export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}

export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}

// 使用命名空间
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 别名简化
import val = Validation;
let validator3 = new val.ZipCodeValidator();

六、装饰器

6.1 方法装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;

descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
const result = originalMethod.apply(this, args);
console.log(`Result: ${result}`);
return result;
};

return descriptor;
}

class MyClass {
@log
add(a: number, b: number): number {
return a + b;
}
}

const obj = new MyClass();
obj.add(1, 2); // 会输出日志

6.2 类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

// 带参数的类装饰器
function decoratorFactory(name: string) {
return function(constructor: Function) {
console.log(`Decorator called with name: ${name}`);
};
}

@decoratorFactory("example")
class Example {}

6.3 属性装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 属性装饰器
function format(formatString: string) {
return function(target: any, propertyKey: string) {
let value = target[propertyKey];

const getter = function() {
return `${formatString} ${value}`;
};

const setter = function(newVal: string) {
value = newVal;
};

Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}

class FormattedClass {
@format("Name:")
name: string = "John";
}

const formatted = new FormattedClass();
console.log(formatted.name); // "Name: John"

七、配置与工具

7.1 tsconfig.json配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

7.2 常见编译选项

选项 说明
target 编译目标版本(ES3/ES5/ES6/ES2020等)
module 模块系统(CommonJS/AMD/UMD/ES6等)
strict 启用所有严格类型检查选项
noImplicitAny 禁止隐式any类型
strictNullChecks 启用严格的null检查
outDir 输出目录
rootDir 源码根目录
declaration 生成.d.ts声明文件
sourceMap 生成source map文件

八、最佳实践

8.1 类型定义建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 优先使用接口定义对象形状
interface User {
id: number;
name: string;
}

// 使用类型别名定义联合类型
type ID = string | number;

// 使用字面量类型限制取值范围
type Status = "pending" | "approved" | "rejected";

// 使用泛型提高代码复用
function processItems<T>(items: T[]): T[] {
return items.filter(item => item !== null);
}

// 善用工具类型
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;
type TodoInfo = Omit<Todo, "completed">;

8.2 类型安全技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用const断言
let config = {
host: "localhost",
port: 3000
} as const;
// config.host = "example"; // Error

// 使用类型谓词进行 narrowing
function isString(value: unknown): value is string {
return typeof value === "string";
}

// 使用Non-null断言(谨慎使用)
function processUser(user?: User) {
const name = user!.name; // 确定user不为null/undefined时使用
}

// 使用可选链和空值合并
const userName = user?.name ?? "Anonymous";

九、总结

TypeScript通过静态类型检查大大提升了JavaScript代码的可维护性和可靠性。本文涵盖了TypeScript的核心特性:

  1. 类型系统:基本类型、联合类型、交叉类型、类型推断
  2. 接口与类型别名:定义对象结构、可选属性、只读属性
  3. 泛型编程:类型参数、约束、映射类型、条件类型
  4. 模块系统:ES模块、CommonJS模块、命名空间
  5. 装饰器:类装饰器、方法装饰器、属性装饰器
  6. 高级类型:类型守卫、映射类型、条件类型

掌握TypeScript的关键在于理解类型系统的思维方式,善用类型推断减少冗余代码,同时利用静态类型检查在编译阶段捕获错误。随着项目规模的增长,TypeScript带来的类型安全优势会越来越明显。

建议学习者:

  • 从基础类型开始,逐步掌握接口和泛型
  • 多阅读优秀开源项目的TypeScript代码
  • 使用strict模式培养良好的类型习惯
  • 关注TypeScript的更新和新特性