TypeScript
一、TypeScript 概述
1.1 什么是 TypeScript
TypeScript 是微软开发的 JavaScript 的超集,在 JS 基础上添加了静态类型系统。TS 代码编译后生成纯 JS 代码,可运行在任何支持 JS 的环境中。
1.2 TypeScript vs JavaScript
|
特性 |
JavaScript |
TypeScript |
|---|---|---|
|
类型系统 |
动态类型 |
静态类型(可选) |
|
编译检查 |
运行时才发现错误 |
编译时即可发现类型错误 |
|
IDE 支持 |
基础 |
强大的智能提示和自动补全 |
|
泛型 |
不支持 |
支持 |
|
接口/枚举 |
不支持 |
支持 |
|
装饰器 |
Stage 3 提案 |
原生支持(实验性) |
|
学习成本 |
低 |
中等 |
1.3 为什么选择 TypeScript
- 编译时捕获类型错误,减少线上 Bug
- 更好的 IDE 智能提示,提升开发效率
- 类型即文档,降低代码维护成本
- 大型项目中优势更明显
二、环境搭建
2.1 安装与编译
# 全局安装
npm install -g typescript
# 查看版本
tsc --version
# 编译单个文件
tsc index.ts
# 编译并监听变化
tsc --watch index.ts
# 初始化 tsconfig.json
tsc --init
2.2 tsconfig.json 关键配置
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
三、基础类型系统
3.1 原始类型
// 布尔
let isDone: boolean = false;
// 数字(所有数字都是浮点数)
let decimal: number = 6;
let hex: number = 0xff;
let binary: number = 0b1010;
// 字符串
let color: string = 'blue';
let greeting: string = `Hello, ${color}`;
// 数组
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
// 元组(固定长度和类型的数组)
let tuple: [string, number] = ['hello', 10];
// 枚举
enum Color { Red, Green, Blue }
let c: Color = Color.Green;
// 枚举支持字符串值
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Deleted = 'DELETED'
}
3.2 特殊类型
// any — 关闭类型检查(尽量少用)
let notSure: any = 4;
notSure = 'maybe a string';
// unknown — 安全的 any(必须先类型收窄才能使用)
let uncertain: unknown = 4;
if (typeof uncertain === 'string') {
console.log(uncertain.toUpperCase());
}
// void — 函数无返回值
function warn(msg: string): void {
console.warn(msg);
}
// never — 永远不会返回(抛异常或死循环)
function error(msg: string): never {
throw new Error(msg);
}
// null / undefined
let u: undefined = undefined;
let n: null = null;
3.3 类型断言
// 告诉编译器"我知道这是什么类型"
let someValue: unknown = 'hello';
// 方式一:尖括号
let strLength: number = (<string>someValue).length;
// 方式二:as(JSX 中只能用这种)
let strLength2: number = (someValue as string).length;
// 非空断言(告诉编译器值一定不为 null/undefined)
let name: string | null = getName();
console.log(name!.length);
四、接口与类型别名
4.1 Interface(接口)
// 基本接口
interface User {
id: number;
name: string;
email: string;
}
// 可选属性
interface Config {
url: string;
method?: string;
timeout?: number;
}
// 只读属性
interface Point {
readonly x: number;
readonly y: number;
}
// 函数类型接口
interface SearchFunc {
(source: string, keyword: string): boolean;
}
// 索引签名
interface StringMap {
[key: string]: string;
}
// 接口继承
interface Admin extends User {
role: string;
permissions: string[];
}
// 接口可以多次声明(自动合并)
interface Window {
title: string;
}
interface Window {
width: number;
}
// Window 现在有 title 和 width 两个属性
4.2 Type Alias(类型别名)
// 基本类型别名
type ID = string | number;
// 联合类型
type Status = 'active' | 'inactive' | 'deleted';
// 交叉类型
type Employee = User & { employeeId: string; department: string };
// 函数类型
type Callback = (data: string) => void;
// 元组
type Point2D = [number, number];
4.3 Interface vs Type
|
特性 |
Interface |
Type |
|---|---|---|
|
声明合并 |
支持 |
不支持 |
|
扩展方式 |
extends |
交叉类型 & |
|
联合类型 |
不支持 |
支持 |
|
元组 |
不支持 |
支持 |
|
映射类型 |
不支持 |
支持 |
|
使用建议 |
优先用 interface 描述对象结构 |
需要联合类型/映射类型时用 type |
五、函数类型
5.1 函数声明与表达式
// 函数声明
function add(x: number, y: number): number {
return x + y;
}
// 函数表达式
const multiply: (x: number, y: number) => number = function(x, y) {
return x * y;
};
// 箭头函数
const divide = (x: number, y: number): number => x / y;
// 可选参数(必须在必选参数之后)
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}`;
}
// 默认参数
function greet2(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}`;
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
5.2 函数重载
// 多个重载签名 + 1 个实现签名
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: string | number | boolean): string {
if (typeof value === 'string') return `"${value}"`;
if (typeof value === 'number') return value.toFixed(2);
return value ? 'true' : 'false';
}
5.3 this 类型
// 显式指定 this 类型(作为第一个参数,编译后会被移除)
interface Card {
suit: string;
card: number;
}
function pickCard(this: Card): string {
return `${this.card} of ${this.suit}`;
}
六、类与面向对象
6.1 类的基本语法
class Animal {
name: string;
static species = 'unknown';
readonly birthDate: Date;
constructor(name: string) {
this.name = name;
this.birthDate = new Date();
}
move(distance: number): void {
console.log(`${this.name} moved ${distance}m.`);
}
static create(name: string): Animal {
return new Animal(name);
}
}
6.2 访问修饰符
class Person {
public name: string;
private age: number;
protected gender: string;
readonly id: number;
// 参数属性简写(构造函数参数前加修饰符,自动声明并赋值)
constructor(
name: string,
private _age: number,
protected _gender: string
) {
this.name = name;
}
// getter / setter
get age(): number {
return this._age;
}
set age(value: number) {
if (value < 0) throw new Error('年龄不能为负数');
this._age = value;
}
}
6.3 继承与抽象类
// 继承
class Dog extends Animal {
bark(): void {
console.log('Woof!');
}
move(distance: number): void {
console.log('Dog running...');
super.move(distance);
}
}
// 抽象类(不能直接实例化)
abstract class Shape {
abstract getArea(): number;
describe(): string {
return `Area: ${this.getArea()}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
6.4 接口实现
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date): void {
this.currentTime = d;
}
}
七、泛型
7.1 泛型函数
// 基本泛型
function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>('hello');
const result2 = identity(42);
// 泛型约束
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength('hello'); // OK
logLength([1, 2, 3]); // OK
// 多泛型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
7.2 泛型接口与泛型类
// 泛型接口
interface Repository<T> {
getById(id: string): T;
getAll(): T[];
create(item: T): T;
update(id: string, item: Partial<T>): T;
delete(id: string): void;
}
// 泛型类
class GenericList<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T {
return this.items[index];
}
}
7.3 泛型工具类型
// Partial — 所有属性变为可选
type PartialUser = Partial<User>;
// Required — 所有属性变为必填
type RequiredUser = Required<PartialUser>;
// Readonly — 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// Pick — 选取指定属性
type UserNameAndEmail = Pick<User, 'name' | 'email'>;
// Omit — 排除指定属性
type UserWithoutEmail = Omit<User, 'email'>;
// Record — 构造对象类型
type PageMap = Record<string, string>;
// Exclude — 从联合类型中排除
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'>; // 'c'
// Extract — 从联合类型中提取
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'c'>; // 'a' | 'c'
// NonNullable — 排除 null 和 undefined
type T3 = NonNullable<string | null | undefined>; // string
// ReturnType — 获取函数返回值类型
type T4 = ReturnType<() => string>; // string
// Parameters — 获取函数参数类型
type T5 = Parameters<(x: number, y: string) => void>; // [number, string]
// Awaited — 获取 Promise 内部类型
type T6 = Awaited<Promise<string>>; // string
八、高级类型
8.1 联合类型与交叉类型
// 联合类型 — 值可以是多种类型之一
type StringOrNumber = string | number;
function formatId(id: StringOrNumber): string {
return `ID: ${id}`;
}
// 交叉类型 — 合并多个类型的所有属性
interface Nameable { name: string; }
interface Ageable { age: number; }
type Person = Nameable & Ageable;
// { name: string; age: number; }
// 类型守卫(type guard)
function printValue(value: string | number): void {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // value 被收窄为 string
} else {
console.log(value.toFixed(2)); // value 被收窄为 number
}
}
8.2 类型守卫
// typeof — 原始类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// instanceof — 类实例守卫
class Cat { meow() {} }
class Dog { bark() {} }
function handleAnimal(animal: Cat | Dog) {
if (animal instanceof Cat) {
animal.meow();
}
}
// in — 属性存在守卫
interface Bird { fly(): void; }
interface Fish { swim(): void; }
function move(pet: Bird | Fish) {
if ('fly' in pet) {
pet.fly();
}
}
// 自定义类型守卫(is)
function isUser(obj: any): obj is User {
return obj && typeof obj.name === 'string' && typeof obj.age === 'number';
}
8.3 条件类型
// 基本条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[]
// infer — 在条件类型中推断类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type D = Unwrap<Promise<string>>; // string
type FirstArg<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;
8.4 映射类型
// 遍历属性并转换
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type PropKey = 'name' | 'age' | 'location';
type ChangeEvent = `on${Capitalize<PropKey>}Change`;
// 'onNameChange' | 'onAgeChange' | 'onLocationChange'
8.5 keyof 与 typeof
// keyof — 获取对象类型的所有键
interface User { name: string; age: number; }
type UserKeys = keyof User; // 'name' | 'age'
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// typeof — 获取值的类型
const config = {
host: 'localhost',
port: 3000,
debug: true
};
type Config = typeof config;
// as const — 将值收窄为字面量类型
const routes = ['home', 'about', 'contact'] as const;
type Route = typeof routes[number]; // 'home' | 'about' | 'contact'
九、模块与声明文件
9.1 ES Module 使用
// 具名导出
export function add(x: number, y: number): number { return x + y; }
// 默认导出
export default class Calculator { }
// 具名导入
import { add, type Point } from './math';
// 默认导入
import Calculator from './calculator';
// 仅导入类型(编译时被擦除)
import type { User } from './types';
9.2 声明文件(.d.ts)
// 为已有的 JS 库声明类型
declare module 'my-library' {
export function doSomething(param: string): number;
export const version: string;
}
// 声明全局变量
declare const APP_VERSION: string;
// 扩展全局命名空间
declare global {
interface Window {
__CUSTOM_DATA__: any;
}
}
// 扩展已有模块
declare module 'express' {
interface Request {
user?: { id: string; name: string };
}
}
十、工程化实践
10.1 代码质量工具
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-explicit-any": "warn"
}
}
10.2 常用类型声明库
npm i -D @types/node # Node.js 类型
npm i -D @types/express # Express 类型
npm i -D @types/react @types/react-dom # React 类型
npm i -D @types/lodash # Lodash 类型
npm i -D @types/jest # Jest 类型
十一、最佳实践
11.1 类型设计原则
- 优先使用 interface 描述对象形状,用 type 处理联合/交叉/映射类型
- 避免 any,不确定类型用 unknown
- 善用泛型约束而不是断言
- 利用字面量类型缩小类型范围
- 善用 readonly 防止意外修改
- 开启 strict 模式从项目初期就保持严格检查
11.2 常见模式
// 模式一:Discriminated Union(可辨识联合)
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return (shape.base * shape.height) / 2;
}
}
// 模式二:Branded Types(品牌类型,防止类型混淆)
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };
// 模式三:Builder Pattern
class RequestBuilder {
private url: string = '';
private method: string = 'GET';
setUrl(url: string): this { this.url = url; return this; }
setMethod(method: string): this { this.method = method; return this; }
build() { return { url: this.url, method: this.method }; }
}
11.3 TypeScript 资源
- 官方文档:https://www.typescriptlang.org/docs/
- TS Playground:https://www.typescriptlang.org/play
- DefinitelyTyped:https://github.com/DefinitelyTyped/DefinitelyTyped
- Type Challenges:https://github.com/type-challenges/type-challenges