TypeScript
介绍 / Introduction¶
TypeScript 是 JavaScript 的**超集**,在 JS 之上引入静态类型系统:类型错误在**编译期**就被发现,而不是等到运行时。编译产物仍是标准 JS,可跑在任何支持 JS 的环境。
Note
类型只在开发/编译期存在,编译后会**擦除**(type erasure),不影响运行时性能。推荐配合 ESLint + Prettier 使用。
配置与编译 / Config & Compile¶
# 安装
npm install -D typescript
npm install -g typescript
# 常用命令
tsc --init # 生成 tsconfig.json
tsc # 按 tsconfig 编译整个项目
tsc app.ts # 编译单文件
npx tsc --noEmit # 只做类型检查,不产出文件(CI 常用)
tsc --watch # 监听模式
tsconfig.json 常用配置速查¶
{
"compilerOptions": {
"target": "ES2020", // 编译目标 JS 版本
"module": "ESNext", // 模块系统
"moduleResolution": "Bundler", // 模块解析策略
"lib": ["ES2020", "DOM"], // 引入哪些内置类型库
"jsx": "react-jsx", // JSX 处理方式
"strict": true, // 开启全部严格检查(强烈建议)
"noImplicitAny": true, // 禁止隐式 any
"strictNullChecks": true, // null/undefined 严格区分
"esModuleInterop": true, // 兼容 CommonJS 默认导入
"skipLibCheck": true, // 跳过 .d.ts 类型检查,加速
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, // 允许 import json
"isolatedModules": true,
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源码根目录
"declaration": true, // 生成 .d.ts
"sourceMap": true,
"baseUrl": ".",
"paths": { // 路径别名
"@/*": ["src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
| 配置项 | 作用 | 建议 |
|---|---|---|
strict |
一键开启全部严格模式 | 新项目必开 |
strictNullChecks |
null/undefined 不可赋给其他类型 |
必开 |
noImplicitAny |
隐式 any 报错 |
必开 |
target |
输出语法版本 | 现代浏览器用 ES2020+ |
esModuleInterop |
简化默认导入 | 一般开 |
skipLibCheck |
跳过依赖类型检查 | 通常开,加速构建 |
Warning
不要用 any 图省事躲过报错,那等于关掉了类型保护。真需要宽泛类型时用 unknown 再收窄。
基础类型 / Basic Types¶
// 原始类型
let str: string = 'hello';
let num: number = 42;
let bool: boolean = true;
let big: bigint = 100n;
let sym: symbol = Symbol('k');
// 数组
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ['a', 'b'];
// 元组:定长、定类型
let tuple: [string, number] = ['age', 18];
// 对象
let obj: { name: string; age?: number } = { name: 'Tom' };
// 函数
let fn: (a: number, b: number) => number = (a, b) => a + b;
// 联合 / 字面量
let id: string | number = 1;
type Dir = 'up' | 'down' | 'left' | 'right';
类型注解 vs 类型推断¶
// 显式注解:变量声明处写类型
let name: string = 'Tom';
// 类型推断:TS 自动推断,无需手写
let age = 18; // 推断为 number
const list = [1, 2, 3]; // 推断为 number[]
Tip
局部变量能推断就交给推断,只在**函数参数、返回值、复杂结构、公共 API**处显式标注,代码更简洁。
特殊类型:any / unknown / never / void / object¶
| 类型 | 含义 | 说明 |
|---|---|---|
any |
任意类型 | 放弃类型检查,尽量少用 |
unknown |
未知类型 | 安全的 any,用前必须收窄 |
never |
永不存在的值 | 抛错函数的返回、穷尽检查兜底 |
void |
无返回值 | 函数无 return 时 |
object |
非原始类型 | 任意对象,不能访问具体属性 |
null / undefined |
空值 | strict 下需显式处理 |
// unknown:用前必须收窄
function parse(input: unknown) {
// input.toUpperCase(); // 报错:unknown 不可直接调用
if (typeof input === 'string') {
return input.toUpperCase(); // 收窄后可用
}
}
// never
function fail(msg: string): never {
throw new Error(msg);
}
// void
function log(msg: string): void {
console.log(msg);
}
接口与类型别名 / Interface & Type¶
// interface:定义对象结构
interface User {
id: number;
name: string;
email?: string; // 可选属性
readonly createdAt: Date; // 只读
}
// type:可表示联合、元组、原始类型等更广
type ID = string | number;
type Point = { x: number; y: number };
type Callback = (data: string) => void;
| 维度 | interface | type |
|---|---|---|
| 描述对象 | 可以 | 可以 |
| 声明合并 | 支持(同名自动合并) | 不支持 |
| 联合/交叉/元组 | 需间接 | 直接写 |
| 实现 implements | 可以 | 可以(对象类型) |
| 扩展 | extends |
& 交叉 |
| 推荐 | 定义对象/公开 API | 联合、工具、组合 |
// interface 扩展
interface Admin extends User {
role: string;
}
// type 组合
type Admin = User & { role: string };
// 声明合并(interface 特有)
interface Window { myGlobal: string; }
interface Window { myOther: number; }
联合、交叉与字面量 / Union & Intersection¶
// 联合:满足其一即可
type Result = { ok: true; data: string } | { ok: false; error: string };
// 交叉:同时满足
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
// 字面量类型:限定取值
type Status = 'pending' | 'success' | 'error';
let s: Status = 'success';
// s = 'done'; // 报错
// 可选与只读
interface Config {
readonly host: string; // 只读
port?: number; // 可选
}
Tip
联合类型收窄后,TS 会通过**可辨识联合(discriminated union)**自动排除不可能分支,结合 switch 做穷尽检查非常安全。
function handle(res: Result) {
if (res.ok) {
console.log(res.data); // 此分支 TS 知道是成功态
} else {
console.error(res.error); // 此分支知道是失败态
}
}
函数 / Functions¶
// 基本签名
function add(a: number, b: number): number {
return a + b;
}
// 可选参数(必须在必选参数之后)
function greet(name: string, title?: string) {
return title ? `${title} ${name}` : name;
}
// 默认参数
function pow(base: number, exp = 2) {
return base ** exp;
}
// 剩余参数
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// 函数类型(箭头)
const mul: (a: number, b: number) => number = (a, b) => a * b;
// 函数重载:多个签名 + 一个实现
function toArray(value: string): string[];
function toArray(value: number): number[];
function toArray(value: string | number): (string | number)[] {
return Array.isArray(value) ? value : [value];
}
Note
箭头函数与普通函数类型的区别:需要 this 类型时用 (this: T, ...) => void;方法内可用 this 参数显式声明类型。
泛型 / Generics¶
泛型函数¶
// 保持入参和返回值类型一致
function identity<T>(value: T): T {
return value;
}
const a = identity<string>('hi'); // 也可让 TS 自动推断
const b = identity(42); // 推断为 number
// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
泛型接口 / 类型别名¶
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
const res: ApiResponse<User[]> = { code: 0, data: [], message: 'ok' };
type Box<T> = { value: T };
泛型类¶
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
const s = new Stack<number>();
s.push(1);
泛型约束(extends)¶
// 约束 T 必须有 length 属性
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
// keyof 约束:K 必须是 T 的键
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Tom' };
const name = get(user, 'name'); // 类型为 string
默认类型参数¶
interface Pager<T = unknown> {
list: T[];
total: number;
}
const p1: Pager = { list: [], total: 0 }; // 用默认
const p2: Pager<User> = { list: [], total: 0 }; // 指定
Tip
泛型是「类型的参数」。当你发现两个地方的类型应该互相绑定,或你想写一个对多种类型都工作的函数/容器,就该用泛型。
枚举 / Enums¶
// 数字枚举:自动递增
enum Status {
Pending, // 0
Success, // 1
Failed, // 2
}
// 字符串枚举:可读性好、调试友好
enum Role {
Admin = 'admin',
User = 'user',
}
// 常量枚举:编译期内联,减少产物
const enum Color {
Red = 'red',
Blue = 'blue',
}
| 方案 | 产物 | 优点 | 缺点 |
|---|---|---|---|
enum |
生成运行时对象 | 类型 + 值一体 | 增加产物,跨模块麻烦 |
const enum |
编译期内联 | 无运行时开销 | 受 isolatedModules 限制 |
字面量联合 + as const |
纯类型 | 零产物、灵活 | 需手动维护 |
// 现代推荐:用 as const 对象 + 联合类型替代 enum
const Status = {
Pending: 'pending',
Success: 'success',
Failed: 'failed',
} as const;
type Status = (typeof Status)[keyof typeof Status]; // 'pending' | 'success' | 'failed'
类型断言与守卫 / Assertions & Guards¶
// 1. as 断言:告诉 TS「我知道它是什么」
const el = document.getElementById('app') as HTMLDivElement;
const len = (value as string).length;
// 2. 非空断言 !:排除 null/undefined
const node = document.querySelector('.box')!;
node.innerHTML = 'hi';
// 3. typeof 守卫(原始类型)
function fmt(x: string | number) {
if (typeof x === 'string') return x.toUpperCase();
return x.toFixed(2);
}
// 4. instanceof 守卫(类实例)
function fmtDate(d: Date | string) {
if (d instanceof Date) return d.getTime();
return new Date(d).getTime();
}
// 5. in 守卫(属性存在性)
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ('swim' in animal) animal.swim();
else animal.fly();
}
// 6. 自定义类型守卫:返回 x is T
function isString(x: unknown): x is string {
return typeof x === 'string';
}
Warning
as 和 ! 是「你说服编译器」,编译器**不会帮你验证**。断言错方向会在运行时爆炸,优先用类型守卫做真实收窄。
工具类型速查 / Utility Types¶
| 工具类型 | 作用 | 示例结果 |
|---|---|---|
Partial<T> |
全部属性变可选 | { a?: 1 } |
Required<T> |
全部属性变必选 | { a: 1 } |
Readonly<T> |
全部属性变只读 | 不可重新赋值 |
Pick<T, K> |
挑选部分属性 | Pick<User,'id'> |
Omit<T, K> |
剔除部分属性 | Omit<User,'pwd'> |
Record<K, V> |
构造键值映射 | Record<string, number> |
Exclude<T, U> |
从联合中排除 | Exclude<'a'\|'b','a'> → 'b' |
Extract<T, U> |
从联合中提取 | Extract<'a'\|'b','a'> → 'a' |
ReturnType<F> |
函数返回值类型 | ReturnType<typeof fn> |
Parameters<F> |
函数参数元组类型 | [string, number] |
Awaited<T> |
递归解包 Promise | Awaited<Promise<string>> → string |
NonNullable<T> |
去除 null/undefined |
interface User {
id: number;
name: string;
password: string;
}
type PartialUser = Partial<User>; // 全可选
type UserPreview = Pick<User, 'id' | 'name'>; // 只保留 id/name
type SafeUser = Omit<User, 'password'>; // 去掉密码
type UserMap = Record<string, User>; // 字典
// 函数相关
async function getUser() {
return { id: 1, name: 'Tom' };
}
type UserResult = Awaited<ReturnType<typeof getUser>>; // { id: number; name: string }
// Exclude / Extract
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'c'>; // 'a' | 'c'
Tip
工具类型的实现本质是**映射类型 + 条件类型**。理解 Partial 的写法 { [K in keyof T]?: T[K] },你就能自己写自定义工具类型。
// 自定义工具类型:把所有属性变为可空
type Nullable<T> = { [K in keyof T]: T[K] | null };
// 自定义:把指定键变为可选
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
类与访问修饰符 / Classes¶
class Animal {
public name: string; // 公开,默认
private secret = 'shh'; // 仅本类可访问
protected age: number; // 本类及子类可访问
readonly kind = 'animal'; // 只读,初始化后不可改
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 参数属性:构造函数参数前加修饰符,自动声明并赋值
// constructor(public name: string, private age: number) {}
speak(): void {
console.log(`${this.name} speaks`);
}
// 静态成员
static create(name: string): Animal {
return new Animal(name, 0);
}
}
// 继承
class Dog extends Animal {
constructor(name: string) {
super(name, 3); // 必须先调用 super
}
speak(): void {
console.log('Woof'); // 重写父类方法
}
}
// 抽象类:不能直接实例化,供子类实现
abstract class Shape {
abstract area(): number; // 子类必须实现
describe(): string {
return `面积 ${this.area()}`;
}
}
class Circle extends Shape {
constructor(private r: number) { super(); }
area(): number { return Math.PI * this.r ** 2; }
}
// 接口实现
interface Serializable {
serialize(): string;
}
class Doc implements Serializable {
serialize(): string { return JSON.stringify(this); }
}
| 修饰符 | 本类 | 子类 | 外部 |
|---|---|---|---|
public(默认) |
✅ | ✅ | ✅ |
protected |
✅ | ✅ | ❌ |
private |
✅ | ❌ | ❌ |
readonly |
只读 | 只读 | 只读 |
Note
TypeScript 的 private 是**编译期**约束(#field 才是运行时私有)。需要真正的运行时限定时,用 ES 私有字段 #name。
模块、声明与命名空间 / Modules & Declarations¶
// 命名导出
export const PI = 3.14;
export function area(r: number) { return PI * r ** 2; }
// 默认导出
export default class Calculator {}
// 导入
import Calculator, { PI, area } from './math';
import * as MathUtils from './math';
import type { User } from './types'; // 仅类型导入,编译后删除
// .d.ts:类型声明文件,只写类型不给实现
// types/user.d.ts
declare interface User {
id: number;
name: string;
}
// 声明全局变量/模块
declare const VERSION: string;
declare module '*.svg' {
const content: string;
export default content;
}
// 命名空间(老式组织方式,新代码优先用模块)
namespace Validation {
export function isEmail(s: string): boolean {
return /^[^@]+@[^@]+$/.test(s);
}
}
Validation.isEmail('a@b.com');
Tip
第三方库没有类型时,安装 @types/xxx(如 @types/node);仍缺类型时,自己写 .d.ts 用 declare module 'lib' 兜底。
与 React / Vue 结合 / With Frameworks¶
// React 常见类型
import type { FC, ReactNode, PropsWithChildren, ChangeEvent } from 'react';
import { useState } from 'react';
interface ButtonProps {
label: string;
children?: ReactNode;
onClick?: () => void;
variant?: 'primary' | 'ghost';
}
// 函数组件:推荐直接标注 props 类型
function Button({ label, children, onClick, variant = 'primary' }: ButtonProps) {
return <button onClick={onClick}>{label}{children}</button>;
}
// 泛型组件
interface ListProps<T> {
items: T[];
render: (item: T) => ReactNode;
}
function List<T>({ items, render }: ListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{render(item)}</li>)}</ul>;
}
// 事件类型
const handleChange = (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value);
// 常用 ref / 状态类型
const [user, setUser] = useState<User | null>(null);
// Vue 3 常见类型
import type { Ref, ComputedRef } from 'vue';
import { ref, computed, defineProps } from 'vue';
interface Props {
title: string;
count?: number;
}
const props = defineProps<Props>();
const total = ref<number>(0); // Ref<number>
const double = computed<number>(() => total.value * 2); // ComputedRef<number>
// 显式泛型 ref
const list = ref<User[]>([]);
常见模式与最佳实践 / Best Practices¶
| 实践 | 说明 |
|---|---|
开启 strict |
换来最强的类型安全 |
优先 unknown 而非 any |
强制收窄,避免失控 |
| 类型守卫替代断言 | as/! 只在确知时用 |
对象用 interface,联合用 type |
各取所长 |
用 as const 替代 enum |
零运行时开销、更灵活 |
泛型约束用 extends |
让泛型可控且有提示 |
| 用工具类型组合 | 减少重复类型定义 |
| 公共 API 显式标注 | 私有局部交给推断 |
用 import type |
让仅类型导入在编译后删除 |
// 例:可辨识联合建模 UI 状态
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; message: string };
function render(state: State) {
switch (state.status) {
case 'loading':
return '加载中...';
case 'success':
return state.data.length + ' 条'; // TS 知道有 data
case 'error':
return state.message; // TS 知道有 message
default:
return '';
}
}
Tip
- 不要给每个变量都写类型:能推断就交给推断,只标注「边界」(函数签名、公共 API)。
- 类型报错别急着
@ts-ignore,先想想是类型写错了还是逻辑写错了;确需绕过用@ts-expect-error并附原因。 - 大型项目把类型集中在
types/或*.d.ts,团队成员共用一套契约。