TypeScript 小白逆袭手册

基于 TypeScript v5.x 官方文档提炼,适用 Vue3、React、Node.js 等主流框架。用最少的知识,在最广的战场能用。

基于 TypeScript 官方文档 (v5.x, 2026年6月) 提炼
适用框架:Vue3 · React · Node.js · Next.js · Nuxt · Express


引言:为什么你学不会 TypeScript?

90% 的人学 TS 会犯的错:

  1. 死记类型语法,而不是理解"为什么要这么写"
  2. 一次性学太多,从 enum 到 namespace 全灌进去,大脑爆炸
  3. 脱离项目实战,学了也不知道用在哪

这本手册的目标是:让你用最少的知识,在最多的地方能用


📖 第一章:TS 的本质——给 JS 装上"行车记录仪"

一句话理解 TypeScript

JavaScript = 你开车上路,撞了才知道哪里不对
TypeScript = 车上装了导航 + 行车记录仪,出发前就告诉你这条路有问题

TypeScript 不改变 JS 运行时的行为,它只在你写代码的时候检查类型。编译后,所有类型标注都会被擦除,变成纯 JS。

你只需要记住这个公式

TypeScript = JavaScript + 类型标注

你所有学的 TS 语法,本质都是在做一件事:告诉编译器"这个变量是什么类型"


📖 第二章:类型标注——TS 的核心只有一个技能

2.1 类型标注的三种方式

// 方式1:变量声明时标注
let name: string = "张三";
let age: number = 25;
let isStudent: boolean = true;

// 方式2:函数参数和返回值标注
function add(a: number, b: number): number {
  return a + b;
}

// 方式3:箭头函数标注
const multiply: (x: number, y: number) => number = (x, y) => x * y;

2.2 但是!大多数时候你不需要写类型

TypeScript 有强大的类型推断,你只需要在"边界处"标注类型:

// ❌ 过度标注:没必要
let message: string = "hello"; // TS 能自动推断

// ✅ 需要标注:函数参数
function greet(name: string) {  // 参数必须标注
  return `Hello, ${name}`;      // 返回值 TS 自己推断
}

// ✅ 需要标注:API 返回值
const data: UserInfo = await fetch('/api/user').then(r => r.json());

2.3 黄金法则:只在"进口"和"出口"写类型

        标注参数类型               标注返回类型
            ↓                          ↓
function processData(input: string): Result {
         ↑                                        ↑
  这里是"进口"                              这里是"出口"
  内部变量 TS 自己推断                       中间过程不用管
}

📖 第三章:三张表吃透所有类型

表一:基础类型

类型示例说明
string"hello"字符串
number42, 3.14JS 不分 int/float
booleantrue, false布尔值
nullnull空值
undefinedundefined未定义
bigint100n大整数
symbolSymbol("id")唯一标识

表二:组合类型

类型写法用途
数组string[]Array<string>同类型元素的集合
元组[string, number]固定长度、每位置固定类型
联合类型string | number"或"的关系
交叉类型A & B"且"的关系,合并多个类型
字面量类型"left" | "right"限定为特定值
枚举enum Color { Red, Green }命名常量集(了解即可,推荐用联合类型代替)

表三:特殊类型

类型含义何时使用
any任意类型,关闭检查🚫 尽量不用
unknown任意类型,但必须检查后才能用✅ 替代 any
void无返回值函数没有 return 时
never永远不会发生抛异常、死循环、穷尽检查
object非原始类型较少使用

📖 第四章:对象和接口——TS 的"身份证"

4.1 定义对象形状

// 方式1:类型别名(推荐用于基础类型、联合类型)
type User = {
  name: string;
  age: number;
  email?: string;     // ? 表示可选
  readonly id: number; // readonly 表示只读
};

// 方式2:接口(推荐用于可扩展的对象形状)
interface IUser {
  name: string;
  age: number;
}

// 接口可以扩展
interface IAdmin extends IUser {
  role: "admin" | "superadmin";
}

4.2 type 和 interface 到底怎么选?

记住一句话:能用 interface 就用 interface,不行再换 type。

场景用哪个原因
定义对象形状interface可扩展,性能更好
联合类型typeinterface 不支持
元组类型typeinterface 不支持
需要声明合并interface同名 interface 自动合并
映射类型typeinterface 不支持

4.3 实战中 80% 的情况长这样

// 定义数据模型 → interface
interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

// 定义组件 Props → interface
interface ButtonProps {
  label: string;
  disabled?: boolean;
  onClick: () => void;
}

// 定义 API 响应 → interface
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

// 定义状态联合 → type
type Status = "idle" | "loading" | "success" | "error";

// 定义函数类型 → type
type EventHandler = (event: Event) => void;

📖 第五章:函数——TS 中最常用的复杂类型

5.1 函数类型三要素

// 1. 参数类型
// 2. 返回值类型  
// 3. 可选参数(用 ? 标记)
function fetchUser(
  id: number,           // 必填
  token?: string,       // 可选
  options: RequestOptions = {} // 有默认值
): Promise<User> {      // 返回 Promise<User>
  return api.get(`/users/${id}`);
}

5.2 回调函数类型(Vue/React 必用)

// Vue3 组件事件
const emit = defineEmits<{
  (e: 'update', value: string): void;
  (e: 'delete', id: number): void;
}>();

// React 组件回调
interface Props {
  onChange: (value: string) => void;
  onSubmit: (data: FormData) => Promise<void>;
}

5.3 函数重载(Node.js 必用)

// 同一个函数,不同参数返回不同类型
function getUser(id: number): User;
function getUser(ids: number[]): User[];
function getUser(idOrIds: number | number[]): User | User[] {
  if (Array.isArray(idOrIds)) {
    return idOrIds.map(id => findUser(id));
  }
  return findUser(idOrIds);
}

📖 第六章:泛型——从"会用"到"精通"的关键

6.1 泛型是什么?一张图说清楚

泛型 = 类型参数 = 给类型加了个"占位符"

就像函数参数一样:
function add(a, b)  →  a 和 b 是值的参数
function foo<T>(a: T) → T 是类型的参数

6.2 最简单的泛型

// 问题:这个函数返回值和输入类型一样,但 TS 不知道
function identity(arg: any): any {
  return arg;  // 丢失了类型信息!
}

// 解决:用泛型"记住"类型
function identity<T>(arg: T): T {
  return arg;  // 传入 string → 返回 string
}

// 使用
const result = identity("hello"); // result 类型是 "hello"
const num = identity(42);         // num 类型是 42

6.3 泛型约束:给 T 加条件

// 要求 T 必须有 length 属性
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello");   // ✅ 5
getLength([1, 2, 3]); // ✅ 3
getLength(123);        // ❌ number 没有 length

6.4 泛型在框架中的实际应用

// ---- Vue3 组合式 API ----
// ref 的泛型
const count = ref<number>(0);
const user = ref<User | null>(null);

// composable 泛型
function useFetch<T>(url: string) {
  const data = ref<T | null>(null);
  // ...
  return { data };
}
const { data } = useFetch<User>('/api/user'); // data 类型是 Ref<User | null>

// ---- React Hooks ----
const [items, setItems] = useState<Todo[]>([]);
const [user, setUser] = useState<User | null>(null);

// ---- Node.js 服务 ----
// Express 请求类型
app.get('/api/users/:id', (req: Request<{ id: string }>, res: Response<User>) => {
  const userId = req.params.id;
  res.json({ id: 1, name: "张三" });
});

// 数据库模型泛型
interface Repository<T> {
  findById(id: number): Promise<T | null>;
  findAll(): Promise<T[]>;
  create(data: Omit<T, 'id'>): Promise<T>;
}

📖 第七章:类型收窄——TS 的"智能判断"

7.1 什么是类型收窄

当你有一个联合类型 string | number,TS 会根据你的代码逻辑自动"收窄"类型范围。

function printValue(value: string | number) {
  if (typeof value === "string") {
    // 这里 value 被自动收窄为 string
    console.log(value.toUpperCase());
  } else {
    // 这里 value 被自动收窄为 number
    console.log(value.toFixed(2));
  }
}

7.2 六种收窄方式速查

// 1. typeof 收窄
if (typeof x === "string") { /* x 是 string */ }

// 2. 真值收窄(排除 null/undefined)
if (x) { /* x 不是 null/undefined */ }

// 3. 等值收窄
if (x === "success") { /* x 是 "success" */ }

// 4. in 操作符收窄
if ("swim" in animal) { /* animal 是 Fish */ }

// 5. instanceof 收窄
if (x instanceof Date) { /* x 是 Date */ }

// 6. 自定义类型守卫(最强大)
function isUser(obj: any): obj is User {
  return obj && typeof obj.name === "string" && typeof obj.age === "number";
}

7.3 可辨识联合:实战中的王牌模式

// 这是你在 Vuex/Pinia/Redux 中天天写的模式
type RequestState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: string };

function renderState<T>(state: RequestState<T>) {
  switch (state.status) {
    case "idle":
      return "等待中...";
    case "loading":
      return "加载中...";
    case "success":
      return state.data;  // ✅ TS 知道这里有 data
    case "error":
      return state.error; // ✅ TS 知道这里有 error
    default:
      // 穷尽检查:如果漏了分支,这里会报错
      const _exhaustive: never = state;
      return _exhaustive;
  }
}

📖 第八章:工具类型——TS 的"瑞士军刀"

不用死记硬背,把这张表放在手边,用到时查:

工具类型作用一句话口诀
Partial<T>全部变可选"部分更新"
Required<T>全部变必填"全部必填"
Readonly<T>全部变只读"不可修改"
Pick<T, K>挑选几个属性"挑着用"
Omit<T, K>排除几个属性"去掉几个"
Record<K, V>构造键值对对象"字典/映射"
Exclude<T, U>从联合类型排除"排除"
Extract<T, U>从联合类型提取"提取"
NonNullable<T>去掉 null/undefined"非空"
ReturnType<T>获取函数返回值类型"返回值类型"
Parameters<T>获取函数参数类型"参数类型"

实战应用场景

interface Todo {
  id: number;
  title: string;
  description: string;
  completed: boolean;
  createdAt: Date;
}

// 场景1:更新接口只需要部分字段
function updateTodo(id: number, data: Partial<Todo>) {}
updateTodo(1, { title: "新标题" }); // ✅ 只传需要的

// 场景2:列表只显示部分字段
type TodoListItem = Pick<Todo, "id" | "title" | "completed">;

// 场景3:创建时不需要 id 和 createdAt
type CreateTodoInput = Omit<Todo, "id" | "createdAt">;

// 场景4:API 响应类型(从函数提取)
async function getTodos(): Promise<Todo[]> { /* ... */ }
type TodosResponse = Awaited<ReturnType<typeof getTodos>>; // Todo[]

// 👆 这行代码在做什么?让我们一层层拆开看:

// 第1层:typeof getTodos
// typeof 在 TS 类型世界里,不是 JS 的 typeof 运算符!
// 它的意思是:"请把 getTodos 这个值,翻译成它的类型"
// getTodos 是一个函数 → typeof getTodos 得到这个函数的类型
// 即:() => Promise<Todo[]>

// 第2层:ReturnType<某个函数类型>
// ReturnType 是 TS 内置工具,"请告诉我这个函数的返回值是什么类型"
// ReturnType<() => Promise<Todo[]>> → Promise<Todo[]>

// 第3层:Awaited<某个Promise类型>
// Awaited 是 TS 4.5 新增的工具,"请帮我把 Promise 的外壳剥掉"
// 如果是嵌套 Promise 也会递归剥干净
// Awaited<Promise<Todo[]>> → Todo[]

// 最终结果:TodosResponse = Todo[]

// ===== 为什么需要 Awaited?=====
// async 函数的返回值类型是 Promise<T>,不是 T!
// 但通常我们想要的是"Promise 里面包的那个类型"
// 如果不用 Awaited,你得到的是 Promise<Todo[]>,而不是 Todo[]

// ===== typeof 的两种身份(重点!)=====
// 在 JS 运行时:typeof 42 → "number"(返回字符串)
// 在 TS 类型上下文:typeof someValue → 该值的类型

// ✅ 类型上下文中的 typeof(出现在 type / interface 定义中)
type MyFnType = typeof getTodos;          // () => Promise<Todo[]>

// ✅ 运行时 typeof(出现在表达式/语句中)
const typeName = typeof getTodos;         // "function"(字符串)

📖 第九章:三大框架实战——一套语法打天下

阅读提示:下面所有代码里的类型/变量都标注了来源。
← 来自 xxx 表示这个类型是在哪定义的,方便你追溯。


9.1 Vue3 + TypeScript 必知必会

前置:Vue3 的类型从哪来?

// Vue3 的核心类型来自这些导入
import { ref, computed, watch, defineProps, defineEmits, withDefaults } from 'vue';
//                   ↑                            ↑          ↑
// 来自 vue 包      响应式 API                  编译宏(无需导入)  编译宏辅助函数

// ref<T>()       —— 创建响应式变量,T 是你指定的类型
// defineProps<T>() —— 声明组件接收的 props,T 是 props 的类型
// defineEmits<T>() —— 声明组件发出的事件,T 是事件签名类型
// withDefaults() —— 给 defineProps 的可选属性设置默认值

1. defineProps 类型标注 —— 组件接收的数据

// ===== 使用场景:父组件给子组件传数据 =====
// 父组件:<UserCard title="用户列表" :count="10" />
// 子组件:通过 defineProps 声明"我能接收什么数据"

// 基础版:纯类型声明
const props = defineProps<{
  title: string;    // 必传的标题
  count?: number;   // 可选的计数(?: = 可以不传)
}>();

// 带默认值版:用 withDefaults 包裹
const props = withDefaults(defineProps<{
  title: string;
  count?: number;
}>(), {
  count: 0,  // 如果父组件没传 count,默认用 0
});

// props.title  → 类型是 string,可以直接用
// props.count  → 类型是 number,因为有默认值所以不会是 undefined

2. defineEmits 类型标注 —— 组件发出的事件

// ===== 使用场景:子组件通知父组件"发生了什么" =====
// 父组件:<SearchBox @update:modelValue="handleSearch" @submit="handleSubmit" />
// 子组件:通过 defineEmits 声明"我会发出什么事件"

const emit = defineEmits<{
  // 事件名             参数类型
  (e: 'update:modelValue', value: string): void;  // v-model 绑定的值变了
  (e: 'submit', data: FormData): void;             // 用户点了提交按钮
}>();

// 使用 emit 触发事件:
// emit('update:modelValue', '搜索关键词');  ← value 必须是 string
// emit('submit', formData);                 ← data 必须是 FormData
// FormData 是浏览器内置的 Web API 类型,代表表单数据

3. 模板 ref 类型 —— 获取 DOM 或子组件引用

// ===== 使用场景:在模板中获取 DOM 元素或子组件实例 =====
// 模板:<input ref="inputRef" /> 或 <MyComponent ref="componentRef" />

// 假设有一个子组件 MyComponent,通过 defineExpose 暴露方法
// 文件:@/components/MyComponent.vue
// defineExpose({ focus: () => { /* ... */ } });

import MyComponent from '@/components/MyComponent.vue';
//     ↑ 这是一个 Vue SFC 组件,import 进来后是一个"组件对象"

// 场景A:获取普通 DOM 元素
const inputRef = ref<HTMLInputElement | null>(null);
//                   ↑ HTMLInputElement 是浏览器内置类型
//                     | null 表示初始挂载前是 null

// 场景B:获取子组件实例(用 InstanceType 提取组件暴露的类型)
const componentRef = ref<InstanceType<typeof MyComponent> | null>(null);
//                       ↑ InstanceType<typeof MyComponent>
//                         拆解:typeof MyComponent → 组件的类型
//                               InstanceType<...>  → 组件实例化后的类型
//                         结果:组件通过 defineExpose 暴露的方法都有类型提示

4. Composable 泛型 —— 可复用的组合式逻辑

// ===== 使用场景:封装可复用的数据请求逻辑 =====
// 这个 composable 返回 { data, error, loading, fetch }
// 通过泛型 T 让调用方指定 data 的类型

// 先定义好数据类型(通常放在 @/types/ 目录下)
// 文件:@/types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

// 文件:@/composables/useApi.ts
import { ref } from 'vue';  // ref 来自 vue 包

export function useApi<T>(url: string) {
  // T 是泛型占位符,调用时传入具体类型,如 useApi<User[]>('/api/users')

  const data = ref<T | null>(null);
  //             ↑ 初始值为 null,类型是 T | null(请求完成前是 null)
  const error = ref<string | null>(null);
  //             ↑ 错误信息,初始值为 null
  const loading = ref(false);
  //             ↑ TS 自动推断为 Ref<boolean>

  async function fetch() {
    loading.value = true;
    try {
      const res = await fetch(url);
      data.value = await res.json();  // 返回值被类型检查为 T
    } catch (e) {
      // e 的类型是 unknown(catch 中的错误默认是 unknown)
      error.value = (e as Error).message;  // 用 as 断言为 Error 类型
    } finally {
      loading.value = false;
    }
  }

  return { data, error, loading, fetch };
}

// ===== 在组件中使用 =====
import { useApi } from '@/composables/useApi';
import type { User } from '@/types/user';

// 传入泛型 User[] → data 的类型是 Ref<User[] | null>
const { data } = useApi<User[]>('/api/users');
//       ↑ data.value 的类型是 User[] | null
//         data.value?.[0]?.name 有完整代码提示!

5. Pinia Store 类型 —— 全局状态管理

// ===== 使用场景:管理购物车的全局状态 =====
// 先定义数据类型(通常放在 @/types/ 目录下)
// 文件:@/types/cart.ts
export interface CartItem {
  // 购物车中的商品
  id: number;        // 商品 ID
  name: string;      // 商品名称
  price: number;     // 单价
  quantity: number;  // 数量
}

// 文件:@/stores/cart.ts
import { defineStore } from 'pinia';  // defineStore 来自 pinia 包
import type { CartItem } from '@/types/cart';  // 导入上面定义的类型

// Store 的状态类型
interface CartState {
  items: CartItem[];     // 购物车商品列表
  totalPrice: number;    // 总价
}

export const useCartStore = defineStore('cart', {
  // state 必须显式标注返回类型
  state: (): CartState => ({
    items: [],
    totalPrice: 0,
  }),

  // getters:state 参数自动推断为 CartState
  getters: {
    itemCount: (state): number => state.items.length,
    //          ↑ state 的类型是 CartState(由 state() 的返回值推断)
  },

  // actions:this 自动拥有 state 和 getters
  actions: {
    addItem(item: CartItem): void {
      //            ↑ 来自 @/types/cart 的定义
      this.items.push(item);  // this.items 类型是 CartItem[]
    },
  },
});

// ===== 在组件中使用 =====
// const cartStore = useCartStore();
// cartStore.items        → CartItem[](有完整类型提示)
// cartStore.addItem(...) → 参数必须有 id, name, price, quantity

📋 Vue3 框架类型速查表(日常高频 15 个)

每个类型都标注了来源,附带使用场景和代码示例。


1. Ref<T> — 来自 vue

ref() 创建的响应式引用类型,通过 .value 读写值。

import { ref, type Ref } from 'vue'

// 场景:声明一个可变的响应式变量
const count: Ref<number> = ref(0)
count.value++  // ✅ 有完整类型提示

// 场景:Composable 函数返回响应式值
function useCounter(): { count: Ref<number>; increment: () => void } {
  const count = ref(0)
  const increment = () => count.value++
  return { count, increment }
}
// 解释:Ref<number> 告诉调用方"这是响应式的,记得 .value"

2. ComputedRef<T> — 来自 vue

computed() 返回的计算属性类型,只读 .value

import { computed, type ComputedRef } from 'vue'

// 场景:声明一个依赖其他值的派生状态
const doubled: ComputedRef<number> = computed(() => count.value * 2)
// doubled.value → 自动推导,类型安全
// doubled.value = 100  → ❌ 编译报错,computed 只读

3. MaybeRef<T> — 来自 vue

T | Ref<T>,表示"可以传普通值,也可以传响应式值"。写工具函数时最常用。

import { unref, type MaybeRef } from 'vue'

// 场景:写一个工具函数,参数既接受普通值也接受 Ref
function formatPrice(price: MaybeRef<number>): string {
  const p = unref(price)  // unref 自动处理:Ref 取 .value,普通值原样返回
  return `¥${p.toFixed(2)}`
}

formatPrice(100)           // ✅ 传普通数字
formatPrice(ref(200))      // ✅ 传 Ref
// 解释:MaybeRef 让你的函数更灵活,调用方不用先 .value 再传

4. InjectionKey<T> — 来自 vue

provide/inject 的类型安全钥匙。不用它,inject 返回的是 unknown

import { inject, provide, type InjectionKey } from 'vue'

// 场景:跨组件共享用户信息,保证类型安全
// 定义在 @/types/injection-keys.ts 中
export const USER_KEY: InjectionKey<Ref<User>> = Symbol('user')

// 祖先组件 provide
const user = ref<User>({ id: 1, name: '张三' })
provide(USER_KEY, user)

// 子孙组件 inject —— 类型自动推导为 Ref<User>
const user = inject(USER_KEY)  // 类型:Ref<User> | undefined
// user.value.name → 有完整代码提示 ✅
// 解释:用 Symbol + InjectionKey 避免魔法字符串,同时获得类型安全

5. ComponentPublicInstance — 来自 vue

通过 template ref 获取组件实例时的类型。

import { ref, type ComponentPublicInstance } from 'vue'
import MyModal from './MyModal.vue'

// 场景:在父组件中操作子组件暴露的方法
const modalRef = ref<InstanceType<typeof MyModal> | null>(null)
//                          ↑ InstanceType 提取组件的实例类型
// 也可以直接用 ComponentPublicInstance 做更宽泛的类型标注

// 模板中:<MyModal ref="modalRef" />
// 脚本中:modalRef.value?.open()
// 解释:用 InstanceType<typeof 组件> 可以获得该组件 defineExpose 暴露的精确类型

6. VNode — 来自 vue

虚拟 DOM 节点的类型,h() 函数和渲染函数的返回值。

import { h, type VNode } from 'vue'

// 场景:用渲染函数动态创建元素
function renderIcon(icon: string): VNode {
  return h('span', { class: `icon-${icon}` })
}
// 解释:VNode 是 Vue 虚拟 DOM 的基本单元,渲染函数必须返回它

7. ExtractPropTypes<T> — 来自 vue

defineProps 的类型参数中提取纯 Props 类型,供外部文件引用。

import { type ExtractPropTypes } from 'vue'

// 场景:子组件暴露 props 类型给父组件用
// 文件:MyButton.vue
const props = defineProps({
  label: { type: String, required: true },
  disabled: { type: Boolean, default: false }
})
export type MyButtonProps = ExtractPropTypes<typeof props>
// 结果:{ label: string; disabled: boolean }

// 父组件中使用:
import MyButton, { type MyButtonProps } from './MyButton.vue'
const btnConfig: MyButtonProps = { label: '提交', disabled: true }
// 解释:ExtractPropTypes 避免你在多处重复定义 Props 类型

8. WatchStopHandle — 来自 vue

watch() / watchEffect() 的返回值类型,调用它停止监听。

import { watch, type WatchStopHandle } from 'vue'

// 场景:在组件卸载前手动停止监听
const stop: WatchStopHandle = watch(source, (val) => {
  console.log('changed:', val)
})
// 当不再需要监听时
stop()  // 停止监听

9. App — 来自 vue

createApp() 返回的应用实例类型。写插件或单元测试时用到。

import { createApp, type App, type Plugin } from 'vue'

// 场景:写一个 Vue 插件
const myPlugin: Plugin = {
  install(app: App) {
    // app 类型是 App,有 .component() .directive() .provide() 等方法
    app.provide('apiUrl', 'https://api.example.com')
  }
}

10. Directive<T, V> — 来自 vue

自定义指令的类型。T = 绑定的 DOM 元素类型,V = 指令绑定的值类型。

import { type Directive } from 'vue'

// 场景:写一个 v-focus 指令
const vFocus: Directive<HTMLInputElement, boolean> = {
  mounted(el, binding) {
    // el → HTMLInputElement 类型,有 .focus() 方法 ✅
    // binding.value → boolean 类型
    if (binding.value) el.focus()
  }
}
// 解释:泛型让 el 和 binding 都有精确类型,不会出现 any

11. Component — 来自 vue

表示任意 Vue 组件的类型,动态组件或组件注册时用到。

import { type Component } from 'vue'

// 场景:动态渲染不同组件
const tabs: { label: string; component: Component }[] = [
  { label: '首页', component: HomeView },
  { label: '设置', component: SettingsView },
]
// 解释:Component 是 Vue 组件的通用类型,可接受 .vue 导入的任何组件

12. Slot / Slots — 来自 vue

Slot 是单个插槽的函数签名,Slots 是插槽集合。

import { type Slot, type Slots } from 'vue'

// 场景:用渲染函数访问插槽
const renderSlotContent = (slots: Slots) => {
  // slots.default → Slot | undefined
  // slots.header?.(props) → VNode[] | undefined
  const defaultSlot: Slot | undefined = slots.default
  if (defaultSlot) {
    return defaultSlot()  // 调用插槽函数获取 VNode 数组
  }
}

13. ComponentCustomProperties — 来自 vue

扩展 Vue 全局属性类型,如 this.$httpthis.$api

// 文件:src/types/vue.d.ts
import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios    // 全局 axios 实例
    $api: {                // 全局 API 对象
      getUsers: () => Promise<User[]>
    }
  }
}
// 之后在所有 .vue 文件中,this.$http、this.$api 都有类型提示

14. GlobalComponents — 来自 vue

声明全局注册的组件类型,让模板中使用全局组件时有类型提示。

// 文件:src/types/components.d.ts
import MyButton from '@/components/MyButton.vue'

declare module 'vue' {
  interface GlobalComponents {
    MyButton: typeof MyButton
    // 之后在任何 .vue 模板中 <MyButton> 都有 Props 类型检查
  }
}

15. MaybeRefOrGetter<T> — 来自 vue

T | Ref<T> | (() => T),watch 等 API 的 source 类型。

import { watch, type MaybeRefOrGetter } from 'vue'

// 场景:写一个监听工具,兼容三种传参方式
function whenever(source: MaybeRefOrGetter<boolean>, callback: () => void) {
  watch(source, (val) => { if (val) callback() })
}
whenever(ref(true), () => console.log('ready'))  // ✅ 传 Ref
whenever(() => isReady.value, () => console.log('ready'))  // ✅ 传 Getter

记忆口诀Ref 系管响应式,Extract 系提取类型,Maybe 系灵活传参,InjectionKey 跨组件安全传值。


9.2 React + TypeScript 必知必会

前置:React 的类型从哪来?

// React 的核心类型来自这些导入
import { useState, useRef, createContext, useContext } from 'react';
//       ↑          ↑       ↑              ↑
// React 包的 Hooks 和 API(从 'react' 导入)

import type { FC, ReactNode, ChangeEvent, FormEvent, MouseEvent } from 'react';
//          ↑    ↑          ↑             ↑           ↑
// React 的类型定义(用 type 关键字只导入类型,编译后不产生代码)

1. 函数组件 Props —— 组件接收的数据

// ===== 使用场景:定义一个可复用的按钮组件 =====
// 父组件:<Button label="提交" variant="primary" onClick={handleClick} />

// 文件:@/components/Button.tsx
import type { FC, ReactNode, MouseEvent } from 'react';
//        ↑ FC = FunctionComponent 的简写,React 函数组件的类型

// Props 类型定义
interface ButtonProps {
  label: string;                                 // 按钮文字(必传)
  variant?: "primary" | "secondary" | "danger";  // 按钮样式变体(可选)
  disabled?: boolean;                            // 是否禁用(可选)
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;  // 点击回调(可选)
  //         ↑ MouseEvent 来自 react 包,泛型指定是 button 元素的事件
  children?: ReactNode;  // 插槽内容(可选)
  //        ↑ ReactNode 来自 react 包,表示"任何可以被渲染的内容"
  //          string | number | JSX.Element | null | undefined | ...
}

const Button: FC<ButtonProps> = ({ label, variant = "primary", ...rest }) => {
  //        ↑ FC<ButtonProps> 表示这个组件接收 ButtonProps 类型的 props
  return <button className={variant} {...rest}>{label}</button>;
};

2. useState 类型 —— 组件内部状态

// ===== 使用场景:组件内部的状态管理 =====

// 先定义数据类型(通常放在 @/types/ 目录下)
// 文件:@/types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}
// 文件:@/types/todo.ts
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

// 在组件中使用:
import { useState } from 'react';          // useState 来自 react 包
import type { User } from '@/types/user';  // 导入数据类型
import type { Todo } from '@/types/todo';

// 场景A:可能为 null 的复杂对象
const [user, setUser] = useState<User | null>(null);
//     ↑ user 类型: User | null
//       初始值 null → 还没登录时 user 就是 null
//     setUser(newUser) → newUser 必须是 User | null

// 场景B:数组类型
const [todos, setTodos] = useState<Todo[]>([]);
//     ↑ todos 类型: Todo[]
//       初始值 [](空数组),TS 从泛型 Todo[] 推断元素类型
//     todos.map(todo => todo.title) → 有完整代码提示

// 场景C:字面量联合类型(限定状态值)
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
//     ↑ status 类型: "idle" | "loading" | "error"
//     setStatus("done") → ❌ 类型错误,只能传这三个值之一

3. useRef 类型 —— 引用 DOM 或保存可变值

// ===== 使用场景:获取 input 元素的引用、保存定时器 ID =====
import { useRef } from 'react';  // useRef 来自 react 包

// 场景A:引用 DOM 元素
const inputRef = useRef<HTMLInputElement>(null);
//     ↑ HTMLInputElement 是浏览器内置的 DOM 类型
//       useRef(null) 初始值是 null,类型自动包含 | null
// 使用:inputRef.current?.focus() → 必须先判空

// 场景B:保存定时器 ID(不涉及 DOM)
// setInterval 来自浏览器全局 API,它的返回值类型比较隐晦
// 用 ReturnType<typeof setInterval> 精准获取
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
//     ↑ ReturnType<typeof setInterval> → number(浏览器环境)
//       | null 表示初始值或清空后是 null

// 场景C:保存可变值(不触发重渲染)
const countRef = useRef<number>(0);
//     ↑ 初始值 0,类型推断为 number,不需要 | null
//     countRef.current = 5 → ✅
//     countRef.current = "5" → ❌ 类型错误

4. 事件处理 —— 表单、点击等交互

// ===== 使用场景:处理 input 输入、form 提交、button 点击 =====
// 所有事件类型都来自 react 包
import type { ChangeEvent, FormEvent, MouseEvent } from 'react';

// 场景A:input 输入变化事件
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  //                       ↑ 来自 react 包,泛型指定是 input 元素
  const value = e.target.value;  // value 的类型是 string ✅
  // e.target 的类型是 HTMLInputElement,所以 .value 有提示
};

// 场景B:表单提交事件
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
  //                    ↑ 来自 react 包,泛型指定是 form 元素
  e.preventDefault();  // 阻止默认提交行为
  // FormData 是浏览器内置的 Web API,用于收集表单数据
  const formData = new FormData(e.currentTarget);
};

// 场景C:按钮点击事件
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
  //                   ↑ 来自 react 包,泛型指定是 button 元素
  console.log('按钮被点击了');
};

// ===== 在 JSX 中使用 =====
// <input onChange={handleChange} />
// <form onSubmit={handleSubmit}>...</form>
// <button onClick={handleClick}>点击我</button>

5. Custom Hook 泛型 —— 可复用的逻辑

// ===== 使用场景:封装 localStorage 读写逻辑 =====
// 文件:@/hooks/useLocalStorage.ts
import { useState } from 'react';  // useState 来自 react 包

function useLocalStorage<T>(key: string, initialValue: T) {
  // 泛型 T:调用时指定,如 useLocalStorage<User>('user', defaultUser)
  // key:localStorage 的键名
  // initialValue:如果 localStorage 中没有数据,用这个默认值

  const [storedValue, setStoredValue] = useState<T>(() => {
    // 惰性初始化:只在首次渲染时执行
    try {
      const item = localStorage.getItem(key);     // localStorage 是浏览器全局 API
      return item ? (JSON.parse(item) as T) : initialValue;
      //            ↑ JSON.parse 返回 any,用 as T 断言为我们需要的类型
    } catch {
      return initialValue;  // 解析失败就用默认值
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    //               ↑ 支持直接传值 或 传一个基于旧值计算新值的函数
    const valueToStore = value instanceof Function ? value(storedValue) : value;
    setStoredValue(valueToStore);
    localStorage.setItem(key, JSON.stringify(valueToStore));
  };

  return [storedValue, setValue] as const;
  //     ↑ as const 让返回值变成精确的元组类型 [T, setter]
  //       而不是 (T | setter)[],这样解构后每个变量类型都精确
}

// ===== 在组件中使用 =====
// import { useLocalStorage } from '@/hooks/useLocalStorage';
// import type { User } from '@/types/user';
//
// const defaultUser: User = { id: 0, name: '', email: '' };
// const [user, setUser] = useLocalStorage<User>('current_user', defaultUser);
//       ↑ user 类型: User
//       ↑ setUser 类型: (value: User | ((val: User) => User)) => void

6. Context 类型 —— 跨组件共享数据

// ===== 使用场景:全局共享登录状态 =====
// 文件:@/contexts/AuthContext.tsx
import { createContext, useContext } from 'react';
//       ↑ 来自 react 包

// 先定义数据类型
// 文件:@/types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
}
// 文件:@/types/auth.ts
export interface LoginInput {
  // 登录表单的输入数据
  email: string;
  password: string;
}

// Context 的值类型
interface AuthContextType {
  user: User | null;          // 当前登录用户,未登录时为 null
  login: (credentials: LoginInput) => Promise<void>;  // 登录方法
  //       ↑ credentials 参数类型来自 @/types/auth.ts 的 LoginInput
  logout: () => void;         // 登出方法
}

// 创建 Context,初始值为 null
const AuthContext = createContext<AuthContextType | null>(null);
//     ↑ createContext 来自 react 包
//       泛型指定 Context 的值类型
//       初始值 null → 在 Provider 外部使用时值为 null

// ===== 自定义 Hook:安全地使用 Context =====
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth 必须在 AuthProvider 内部使用');
  }
  return context;  // 类型是 AuthContextType(排除了 null)
}

// ===== 在组件中使用 =====
// import { useAuth } from '@/contexts/AuthContext';
// const { user, login, logout } = useAuth();
// user?.name → 有完整提示
// login({ email: 'test@example.com', password: '123456' }) → 参数有类型检查

📋 React 框架类型速查表(日常高频 18 个)

每个类型都标注了来源,附带使用场景和代码示例。全部来自 react 包。


1. FC<P> — 来自 react

FunctionComponent 的简写,函数组件的类型标注方式。

import { type FC } from 'react'

// 场景:给函数组件标注类型
interface UserCardProps {
  name: string
  avatar: string
  onClick?: () => void
}

const UserCard: FC<UserCardProps> = ({ name, avatar, onClick }) => {
  return <div onClick={onClick}><img src={avatar} />{name}</div>
}
// 解释:FC 自动包含 children 类型,组件写法更简洁
// 注意:React 18+ 后 FC 不再自动包含 children,React 19 又恢复了

2. ReactNode — 来自 react

任何可以被 React 渲染的内容:string | number | JSX.Element | null | undefined | boolean | ReactElement[] | ...

import { type ReactNode } from 'react'

// 场景:定义插槽/children 的类型
interface CardProps {
  title: string
  children?: ReactNode     // 接受任何可渲染内容
  footer?: ReactNode       // 底部区域,可以是文字也可以是 JSX
}

// 使用时非常灵活:
<Card title="公告" footer={<button>确定</button>}>
  <p>这是正文</p>
</Card>
// 解释:ReactNode 是最宽松的"可渲染"类型,children 用它就够了

3. PropsWithChildren<P> — 来自 react

自动给 Props 加上 children?: ReactNode

import { type PropsWithChildren } from 'react'

// 场景:大多数组件都需要 children,但不想每个都手动加
interface SectionProps {
  title: string
  className?: string
}
// 用 PropsWithChildren 自动合并 children
function Section({ title, children, className }: PropsWithChildren<SectionProps>) {
  return <section className={className}><h2>{title}</h2>{children}</section>
}
// 等价于:SectionProps & { children?: ReactNode }

4. CSSProperties — 来自 react

内联 style 的类型,驼峰命名的 CSS 属性。

import { type CSSProperties } from 'react'

// 场景:接收外部传入的样式对象
const containerStyle: CSSProperties = {
  display: 'flex',
  justifyContent: 'center',    // 驼峰 ✅ 而不是 justify-content
  gap: 16,
  padding: '0 16px',
  // background-color: 'red' → ❌ 编译报错
}
// 解释:所有 CSS 属性都有类型提示,写错了立刻报错

5. SetStateAction<S> — 来自 react

useState setter 的参数类型:S | ((prev: S) => S)

import { type SetStateAction, type Dispatch } from 'react'

// 场景:把 setState 作为 prop 传递给子组件
interface ChildProps {
  setValue: Dispatch<SetStateAction<number>>
  //           ↑ 类型: (value: number | ((prev: number) => number)) => void
}
// 解释:Dispatch<SetStateAction<S>> 是 useState setter 的完整类型
// 子组件可以直接 setValue(10) 或 setValue(prev => prev + 1)

6. ChangeEvent<T> — 来自 react

表单元素值改变事件,e.target.value 最常用。

import { type ChangeEvent } from 'react'

// 场景:输入框 onChange 事件处理
function SearchInput() {
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value)  // string,有完整类型
    // e.target.checked → ❌ input 没有 checked 属性
  }
  return <input onChange={handleChange} />
}

// 场景:checkbox 的 onChange
const handleCheck = (e: ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.checked)  // boolean ✅
}
// 解释:泛型指定了元素类型,e.target 就有对应的属性提示

7. MouseEvent<T> — 来自 react

鼠标相关事件(click、dblclick、mousemove 等)。

import { type MouseEvent } from 'react'

// 场景:按钮点击事件
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
  e.preventDefault()   // 有代码提示
  console.log(e.clientX, e.clientY)
}

// 场景:div 上的点击
const handleDivClick = (e: MouseEvent<HTMLDivElement>) => {
  // e.target → EventTarget,需要类型收窄
}
// 解释:泛型决定 e.currentTarget 的类型,是最常用的交互事件

8. FormEvent<T> — 来自 react

表单提交事件。

import { type FormEvent } from 'react'

// 场景:表单提交
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
  e.preventDefault()
  const formData = new FormData(e.currentTarget)
  // 从 e.currentTarget 获取表单数据
}
// 解释:FormEvent 自动帮你拿到了 form 元素的引用

9. KeyboardEvent<T> — 来自 react

键盘事件(keydown、keyup、keypress)。

import { type KeyboardEvent } from 'react'

// 场景:回车键触发搜索
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
  if (e.key === 'Enter') {
    console.log('搜索:', e.currentTarget.value)
  }
}
// 解释:e.key 直接拿到按下的键名,比 e.keyCode 更直观

10. RefObject<T> — 来自 react

useRef 创建的不可变 ref 对象类型。

import { useRef, type RefObject } from 'react'

// 场景:获取 DOM 元素的引用
function AutoFocus() {
  const inputRef: RefObject<HTMLInputElement | null> = useRef(null)
  // 等价于 useRef<HTMLInputElement>(null)
  useEffect(() => {
    inputRef.current?.focus()  // current 是只读的
  }, [])
  return <input ref={inputRef} />
}

11. Context<T> — 来自 react

createContext() 返回的 Context 对象类型。

import { createContext, type Context } from 'react'

// 场景:创建全局共享数据
interface AuthContextType {
  user: User | null
  login: (email: string, password: string) => Promise<void>
  logout: () => void
}

const AuthContext: Context<AuthContextType | null> = createContext<AuthContextType | null>(null)
// 解释:Context 类型确保 Provider 的 value 和 useContext 的返回值类型一致

12. ComponentProps<T> — 来自 react

提取任意组件(HTML 原生或 React 组件)的 Props 类型。

import { type ComponentProps } from 'react'

// 场景:包装原生 button,继承所有原生属性
type ButtonProps = ComponentProps<'button'> & {
  variant?: 'primary' | 'secondary'
}
// ButtonProps 现在包含:onClick, disabled, type, children 等所有 button 属性

// 场景:提取第三方组件的 Props
import { Button as MuiButton } from '@mui/material'
type MuiButtonProps = ComponentProps<typeof MuiButton>
// 解释:不需要翻源码看 Props 定义,一行搞定

13. ComponentPropsWithoutRef<T> — 来自 react

同上,但排除 ref 属性。大多数情况用这个就够了。

import { forwardRef, type ComponentPropsWithoutRef } from 'react'

// 场景:用 forwardRef 包装原生元素,不让外部传 ref 冲突
interface InputProps extends ComponentPropsWithoutRef<'input'> {
  label: string
}
const Input = forwardRef<HTMLInputElement, InputProps>(({ label, ...rest }, ref) => (
  <label>{label}<input ref={ref} {...rest} /></label>
))
// 解释:rest 里没有 ref,不会和 forwardRef 的 ref 冲突

14. ReactElement — 来自 react

JSX 元素的具体类型,比 ReactNode 更精确。

import { cloneElement, type ReactElement } from 'react'

// 场景:克隆并修改已有的 JSX 元素
function withExtraProps(element: ReactElement, extraProps: object) {
  return cloneElement(element, extraProps)
}
// ReactNode 不能 cloneElement,必须用 ReactElement

15. RefCallback<T> — 来自 react

回调 ref 的函数签名:(instance: T | null) => void

import { type RefCallback } from 'react'

// 场景:动态测量 DOM 元素的尺寸
function useMeasure(): [RefCallback<HTMLDivElement>, { width: number }] {
  const [size, setSize] = useState({ width: 0 })
  const ref: RefCallback<HTMLDivElement> = (node) => {
    if (node) setSize({ width: node.offsetWidth })
  }
  return [ref, size]
}
// 解释:回调 ref 比 useRef 更灵活,每次 DOM 挂载/卸载都会调用

16. HTMLAttributes<T> — 来自 react

HTML 元素的通用属性类型(id、className、style、onClick 等)。

import { type HTMLAttributes } from 'react'

// 场景:构建自己的组件库,自定义 div 容器
interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
  fluid?: boolean
}
// ContainerProps 现在自动拥有 className、style、onClick 等所有 div 属性

17. ComponentType<P> — 来自 react

类组件或函数组件的联合类型。用于动态组件渲染。

import { lazy, Suspense, type ComponentType } from 'react'

// 场景:路由配置中的动态导入
const routes: { path: string; component: ComponentType }[] = [
  { path: '/home', component: lazy(() => import('./Home')) },
  { path: '/about', component: lazy(() => import('./About')) },
]
// 解释:ComponentType 兼容 class 组件和 function 组件

18. Dispatch<A> / Reducer<S, A> — 来自 react

useReducer 的核心类型,dispatch 函数和 reducer 函数。

import { useReducer, type Dispatch, type Reducer } from 'react'

// 场景:复杂状态管理
interface State { count: number }
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset'; payload: number }

const reducer: Reducer<State, Action> = (state, action) => {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 }
    case 'decrement': return { count: state.count - 1 }
    case 'reset': return { count: action.payload }
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0 })
// dispatch 类型自动推导为 Dispatch<Action>

记忆口诀FC 管组件,ReactNode 管渲染,PropsWithChildren 自动加 children,ComponentProps 偷 Props,ChangeEvent / MouseEvent 管交互,Context 管共享。


9.3 Node.js + TypeScript 必知必会

前置:Node.js 后端类型从哪来?

// Express 的类型来自 @types/express(需要 npm i -D @types/express)
import type { Request, Response, NextFunction } from 'express';
//         ↑         ↑          ↑
// Express 核心类型,来自 express 包的类型声明

// Prisma 的类型由 prisma generate 命令自动生成
import type { Prisma, User, Post } from '@prisma/client';
//         ↑        ↑      ↑
// 来自 @prisma/client 包,根据你的 schema.prisma 自动生成

// 环境变量的类型来自 @types/node(Node.js 内置类型)
// process.env 的类型声明在 @types/node 中

1. Express 类型增强 —— 扩展 Request 对象

// ===== 使用场景:在 Express 中间件中给 req 添加自定义属性 =====
// 比如 JWT 认证中间件解析 token 后,把用户信息挂到 req.user 上

import type { Request, Response, NextFunction } from 'express';
//        ↑ 来自 express 包的类型声明

// 通过 declare global 扩展 Express 的 Request 接口
declare global {
  namespace Express {
    interface Request {
      // 给 Request 类型添加 user 属性
      user?: {
        id: number;
        role: string;
      };
    }
  }
}
// 做完这个声明后,所有路由中的 req.user 都有类型提示了

// ===== 类型安全的路由处理 =====
// 先定义请求体的类型(通常放在 @/types/ 目录下)
interface CreateUserBody {
  // 创建用户的请求体
  name: string;
  email: string;
  password: string;
}

// 先定义 API 响应的通用格式
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

// Express 的 Request 有三个泛型参数:
// Request<Params, ResBody, ReqBody>
//          ↑        ↑        ↑
//    路由参数   响应体类型  请求体类型
app.post('/api/users', async (
  req: Request<{}, {}, CreateUserBody>,
  //            ↑ Params = {}(没有路由参数如 :id)
  //                ↑ ResBody = {}(不限制响应体类型)
  //                    ↑ ReqBody = CreateUserBody(请求体类型)
  res: Response<ApiResponse<User>>,
  //              ↑ 响应体的数据类型
  next: NextFunction  // 传递给下一个中间件
) => {
  const { name, email, password } = req.body;
  //     ↑ 有完整类型提示:name: string, email: string, password: string ✅
  // 如果写 req.body.xxx → ❌ 类型错误,CreateUserBody 里没有 xxx
});

2. Prisma 数据库模型类型 —— 自动生成的类型

// ===== 使用场景:Prisma ORM 的类型安全查询 =====
// Prisma 会根据你的 schema.prisma 自动生成类型
// 运行 npx prisma generate 后就可以直接导入

// 假设 schema.prisma 中定义了:
// model User { id Int; name String; posts Post[] }
// model Post { id Int; title String; author User }

// 导入自动生成的类型
import type { Prisma, User, Post } from '@prisma/client';
//         ↑ Prisma 命名空间包含各种工具类型
//           User, Post 是模型对应的类型(由 schema.prisma 定义)

// 场景A:带关联查询的结果类型
type UserWithPosts = Prisma.UserGetPayload<{
  include: { posts: true };  // 查询时 include 了 posts 关联
}>;
// UserWithPosts = User & { posts: Post[] }
// 不需要手动写交叉类型,Prisma 自动推导

// 场景B:创建数据的输入类型
type CreateUserInput = Prisma.UserCreateInput;
// 自动包含 User 模型所有必填和可选字段

// 场景C:更新数据的输入类型
type UpdateUserInput = Prisma.UserUpdateInput;
// 所有字段都是可选的(Partial 版)

// ===== 在 Service 层使用 =====
// import { PrismaClient } from '@prisma/client';
// const prisma = new PrismaClient();
//
// const user = await prisma.user.findUnique({ where: { id: 1 } });
// → user 的类型是 User | null,所有字段有提示
//
// const usersWithPosts = await prisma.user.findMany({ include: { posts: true } });
// → 类型是 UserWithPosts[],可以安全访问 usersWithPosts[0].posts

3. 环境变量类型 —— 告别 process.env 的 any

// ===== 使用场景:给 process.env 加上类型安全 =====
// process.env 的类型声明来自 @types/node(npm i -D @types/node)

// 定义环境变量的类型
interface EnvConfig {
  PORT: number;       // 服务器端口
  DATABASE_URL: string;  // 数据库连接字符串
  JWT_SECRET: string;    // JWT 签名密钥
  NODE_ENV: "development" | "production" | "test";  // 运行环境
}

// 统一导出配置对象
const config: EnvConfig = {
  // process.env.xxx 的类型是 string | undefined
  PORT: parseInt(process.env.PORT || "3000"),
  //     ↑ parseInt 把字符串转数字,|| "3000" 提供默认值

  DATABASE_URL: process.env.DATABASE_URL!,
  //            ↑ ! 是非空断言,告诉 TS:"我确定这个值存在"
  //              如果环境变量没设,运行时会报错,但这正是你想要的

  JWT_SECRET: process.env.JWT_SECRET!,

  NODE_ENV: (process.env.NODE_ENV as EnvConfig["NODE_ENV"]) || "development",
  //        ↑ 用 as 断言为字面量联合类型
  //          EnvConfig["NODE_ENV"] → "development" | "production" | "test"
};

// 之后使用 config.PORT → 类型是 number ✅
// config.DATABASE_URL → 类型是 string ✅

4. 中间件类型 —— Express 中间件签名

// ===== 使用场景:写一个 JWT 认证中间件 =====
import type { Request, Response, NextFunction } from 'express';
//        ↑ 来自 express 包

// 扩展 Request 类型,添加 userId
interface AuthRequest extends Request {
  // extends Request:继承 Express 的 Request,保留原有属性
  userId?: number;  // 认证通过后挂载的 userId
}

// 中间件函数的标准签名:(req, res, next) => void
const authMiddleware = (
  req: AuthRequest,     // 使用扩展后的 Request
  res: Response,        // Express 的 Response 类型
  next: NextFunction    // 调用 next() 传递到下一个中间件
): void => {
  // 中间件逻辑...
  // 验证 token 后:req.userId = decoded.id
  next();  // 传递给下一个中间件或路由处理函数
};

// ===== 在路由中使用 =====
// app.get('/api/profile', authMiddleware, (req: AuthRequest, res: Response) => {
//   const userId = req.userId;  // ✅ 有类型提示
// });

5. 异步错误处理 —— 避免 try-catch 地狱

// ===== 使用场景:统一捕获 async 路由处理函数中的错误 =====
// Express 默认不会捕获 async 函数中抛出的错误,需要手动处理

import type { Request, Response, NextFunction } from 'express';

// 定义异步处理函数的类型
type AsyncHandler = (
  req: Request,       // Express 的 Request
  res: Response,      // Express 的 Response
  next: NextFunction  // Express 的 NextFunction
) => Promise<void>;   // 返回 Promise(因为是 async 函数)

// catchAsync:包装异步处理函数,自动捕获错误
const catchAsync = (fn: AsyncHandler) => {
  // 返回一个普通的 Express 中间件(非 async)
  return (req: Request, res: Response, next: NextFunction) => {
    fn(req, res, next).catch(next);
    //                 ↑ 如果 fn 抛出异常,自动传递给 next(err)
    //                    Express 的错误处理中间件会收到这个错误
  };
};

// ===== 使用方式 =====
// 不用 catchAsync 时(错误不会被 Express 捕获):
// app.get('/api/users', async (req, res) => {
//   const users = await UserModel.find();  // 如果报错,Express 不知道
// });

// 用 catchAsync 后(错误自动传递给 Express 错误处理):
// app.get('/api/users', catchAsync(async (req, res) => {
//   const users = await UserModel.find();  // 如果报错 → next(err) → 错误中间件
//   res.json(users);
// }));

📋 Node.js + Express 框架类型速查表(日常高频 18 个)

Node.js 内置类型来自 @types/node,Express 类型来自 @types/express,Prisma 来自 @prisma/client


一、Express 核心类型(来自 @types/express

1. Request<P, ResBody, ReqBody, ReqQuery> — 来自 express

Express 请求对象类型,5 个泛型参数分别对应:路由参数、响应体、请求体、查询参数、本地变量。

import { type Request } from 'express'

// 场景:带路由参数和请求体的 POST 接口
interface CreateBody { title: string; content: string }

app.post('/articles/:categoryId', (
  req: Request<{ categoryId: string }, {}, CreateBody, { draft?: string }>,
  //           ↑ Params        ↑ ResBody ↑ ReqBody  ↑ ReqQuery
  res
) => {
  req.params.categoryId   // string ✅
  req.body.title          // string ✅
  req.query.draft         // string | undefined ✅
  // req.body.xxx → ❌ 报错,CreateBody 里没有 xxx
})
// 解释:泛型越精确,req 的属性提示越完整,减少运行时错误

2. Response<ResBody> — 来自 express

Express 响应对象类型,泛型指定 res.json() 返回的数据类型。

import { type Response } from 'express'

// 场景:统一 API 响应格式
interface ApiResponse<T> { code: number; data: T; message: string }

app.get('/users', async (req, res: Response<ApiResponse<User[]>>) => {
  const users = await UserService.findAll()
  res.json({ code: 0, data: users, message: 'ok' })
  // res.json({ code: 0 }) → ❌ 缺少 data 和 message
})
// 解释:Response 泛型约束了 res.json() 的参数类型,保证响应格式统一

3. NextFunction — 来自 express

中间件的 next 回调类型:(err?: any) => void

import { type NextFunction, type Request, type Response } from 'express'

// 场景:JWT 认证中间件
function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization
  if (!token) return next(new Error('未登录'))  // next(err) → 触发错误处理
  // 验证通过
  next()  // next() → 进入下一个中间件
}
// 解释:next 是 Express 中间件的核心机制,NextFunction 明确其类型

4. RequestHandler<P, ResBody, ReqBody, ReqQuery> — 来自 express

路由处理器的完整签名类型,等同于 (req, res, next) => void | Promise<void>

import { type RequestHandler } from 'express'

// 场景:抽离路由处理逻辑到独立文件
// 文件:@/controllers/user.controller.ts
export const getUsers: RequestHandler = async (req, res, next) => {
  try {
    const users = await UserService.findAll()
    res.json(users)
  } catch (err) {
    next(err)
  }
}
// 文件:@/routes/user.routes.ts
router.get('/users', getUsers)  // ✅ 类型匹配

// 解释:用 RequestHandler 标注控制器函数,确保签名正确

5. ErrorRequestHandler — 来自 express

错误处理中间件的签名类型,有 4 个参数(多了 err)。

import { type ErrorRequestHandler } from 'express'

// 场景:全局错误处理中间件
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  console.error(err.stack)
  res.status(500).json({ code: -1, message: err.message || '服务器错误' })
}
app.use(errorHandler)
// 解释:4 个参数的中间件 Express 自动识别为错误处理

6. Router — 来自 express

express.Router() 返回的路由器类型。

import { Router, type Router as RouterType } from 'express'

// 场景:模块化路由
const router: RouterType = Router()
router.get('/profile', authMiddleware, getProfile)
router.put('/profile', authMiddleware, updateProfile)
export default router
// 解释:Router 类型确保 .get .post .use 等方法的调用类型安全

7. CookieOptions — 来自 express

res.cookie() 的配置选项类型。

import { type CookieOptions } from 'express'

// 场景:设置 cookie 时的配置
const cookieOpts: CookieOptions = {
  httpOnly: true,        // 仅 HTTP 访问,JS 无法读取
  secure: true,          // 仅 HTTPS 传输
  sameSite: 'strict',    // 严格同站策略
  maxAge: 7 * 24 * 60 * 60 * 1000,  // 7 天
}
res.cookie('token', tokenValue, cookieOpts)
// 解释:CookieOptions 确保配置项拼写正确,有代码提示

二、Node.js 内置类型(来自 @types/node

8. NodeJS.ErrnoException — 来自 @types/node 全局

Node.js 风格的回调错误类型,带 errnocodesyscall 等属性。

import fs from 'fs'

// 场景:处理文件操作中的 Node.js 错误
fs.readFile('/path/to/file', (err: NodeJS.ErrnoException | null, data: Buffer) => {
  if (err) {
    if (err.code === 'ENOENT') {
      console.log('文件不存在')  // err.code 有精确的字面量提示
    }
    return
  }
  // data 是 Buffer
})
// 解释:NodeJS.ErrnoException 比普通 Error 多了 code/errno/syscall 等属性

9. Buffer — 来自 @types/nodebuffer 模块)

二进制数据缓冲区类型。

import { type Buffer } from 'buffer'

// 场景:处理文件上传的二进制数据
function processFile(data: Buffer, filename: string) {
  const base64 = data.toString('base64')
  const size = data.length  // 字节数
  // data 有 .toString() .slice() .indexOf() 等方法
}
// 解释:Buffer 是 Node.js 处理二进制数据的核心类型

10. NodeJS.Timeout — 来自 @types/node 全局

setTimeout() 返回值的精确类型。

// 场景:在组件/类中保存定时器引用,以便清理
class Scheduler {
  private timer: NodeJS.Timeout | null = null

  start() {
    this.timer = setTimeout(() => { /* ... */ }, 1000)
  }

  stop() {
    if (this.timer) {
      clearTimeout(this.timer)  // clearTimeout 接受 NodeJS.Timeout
      this.timer = null
    }
  }
}
// 解释:NodeJS.Timeout 比 number 更类型安全,clearTimeout 明确接受此类型

11. NodeJS.ProcessEnv — 来自 @types/node 全局

process.env 的类型,默认是 Dict<string>(所有值都是 string | undefined)。

// 场景:增强 process.env 的类型,避免每次都用 as string
// 文件:src/types/env.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      PORT: string
      DATABASE_URL: string
      JWT_SECRET: string
      NODE_ENV: 'development' | 'production' | 'test'
    }
  }
}
// 之后 process.env.JWT_SECRET 直接是 string 类型 ✅
// 解释:扩展 ProcessEnv 后,不用每次写 process.env.XXX as string

12. URL — 来自 @types/nodeurl 模块)

URL 解析结果的类型。

// 场景:解析和构造 URL
const myUrl: URL = new URL('https://example.com/api/users?page=1&size=10')
myUrl.searchParams.get('page')   // string | null
myUrl.pathname                   // '/api/users'
myUrl.hostname                   // 'example.com'
// 解释:URL 类型提供了结构化访问 URL 各部分的能力

13. Readable / Writable — 来自 @types/nodestream 模块)

Node.js 流的类型,处理大文件或数据管道。

import { createReadStream, createWriteStream } from 'fs'
import { type Readable, type Writable } from 'stream'
import { pipeline } from 'stream/promises'

// 场景:大文件拷贝(流式处理,不占内存)
async function copyFile(src: string, dest: string): Promise<void> {
  const readStream: Readable = createReadStream(src)
  const writeStream: Writable = createWriteStream(dest)
  await pipeline(readStream, writeStream)
}
// 解释:Readable/Writable 是 Node.js 流的基础类型

14. EventEmitter — 来自 @types/nodeevents 模块)

Node.js 事件发射器基类。

import { EventEmitter } from 'events'

// 场景:自定义事件驱动架构
interface MyEvents {
  login: (userId: number) => void
  logout: (userId: number) => void
}

class AppEventBus extends EventEmitter {
  // 带类型的事件发射
  emit<K extends keyof MyEvents>(event: K, ...args: Parameters<MyEvents[K]>): boolean {
    return super.emit(event, ...args)
  }

  on<K extends keyof MyEvents>(event: K, listener: MyEvents[K]): this {
    return super.on(event, listener)
  }
}
// 解释:EventEmitter 是 Node.js 事件驱动编程的基石

三、Prisma ORM 类型(来自 @prisma/client

15. Prisma.ModelNameGetPayload<T> — 来自 @prisma/client

从查询选项中推导出返回结果的精确类型。

import { type Prisma } from '@prisma/client'

// 场景:提取关联查询的结果类型
const userWithPosts = {
  include: { posts: true }
} satisfies Prisma.UserFindManyArgs

type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
// 结果:{ id: number; name: string; posts: Post[]; ... }
// 解释:不用手动定义关联查询的类型,Prisma 自动推导

16. Prisma.ModelNameCreateInput — 来自 @prisma/client

创建记录所需的输入类型。

import { type Prisma } from '@prisma/client'

// 场景:Service 层接收创建数据
async function createUser(data: Prisma.UserCreateInput) {
  return prisma.user.create({ data })
}
// data 类型自动包含 name, email, password 等必填字段
// 调用方必须传所有 required 字段,否则编译报错

17. Prisma.ModelNameUpdateInput — 来自 @prisma/client

更新记录的类型,所有字段都是可选的。

import { type Prisma } from '@prisma/client'

// 场景:部分更新用户信息
async function updateUser(id: number, data: Prisma.UserUpdateInput) {
  return prisma.user.update({ where: { id }, data })
}
// data 可以是 { name: '新名字' },不必传全部字段
// 解释:UpdateInput 所有字段都加了 ?,允许部分更新

18. Prisma.TransactionClient — 来自 @prisma/client

事务客户端类型,接口和 PrismaClient 完全一致。

import { type PrismaClient, type Prisma } from '@prisma/client'

// 场景:在事务中执行多个数据库操作
async function transferMoney(tx: Prisma.TransactionClient, from: number, to: number, amount: number) {
  await tx.account.update({ where: { id: from }, data: { balance: { decrement: amount } } })
  await tx.account.update({ where: { id: to }, data: { balance: { increment: amount } } })
}

// 使用事务
await prisma.$transaction(tx => transferMoney(tx, 1, 2, 100))
// 解释:用 TransactionClient 类型,函数既可以独立调用也可以在事务中复用

记忆口诀:Express 核心三件套 Request/Response/NextFunction,Node.js 常用 Buffer/Stream/EventEmitter/ProcessEnv,Prisma 按 ModelName + 操作 + Input/Payload 自动生成。


📖 附章:import type 完全指南——类型怎么引用?

一、三种导入方式对比

// 方式1:普通 import(导入的是值,运行时存在)
import { ref, watch, computed } from 'vue'
//        ↑ 这些是函数,运行时真实存在,编译后保留在 JS 中

// 方式2:import type(只导入类型,编译后完全消失)
import type { Ref, ComputedRef, MaybeRef } from 'vue'
//             ↑ 这些是纯类型,只在 TS 编译期存在

// 方式3:混合导入(值 + 类型混在一行)
import { ref, type Ref } from 'vue'
//       ↑ 值    ↑ 类型

核心区别

import { X }import type { X }
X 是函数/变量✅ 正确用法❌ 报错,因为编译后消失了
X 是类型/接口⚠️ 能用,但不推荐✅ 正确用法
编译到 JS 中✅ 保留❌ 完全擦除
循环引用可能出问题✅ 安全

二、怎么判断"该不该加 type"?

黄金法则:问自己——"这个东西运行时存在吗?"

// ✅ 这些是函数,运行时存在 → 普通 import
import { ref, watch, computed, reactive, provide, inject } from 'vue'
import { useState, useEffect, useRef, createContext } from 'react'
import express from 'express'

// ✅ 这些是类型/接口,仅编译期存在 → import type
import type { Ref, ComputedRef, InjectionKey, Component, VNode } from 'vue'
import type { FC, ReactNode, ChangeEvent, MouseEvent, FormEvent } from 'react'
import type { Request, Response, NextFunction, RequestHandler } from 'express'

一句话:如果你不需要在代码里调用它、new 它、赋值给它,而只是写在 : 后面当类型标注 → 用 import type


三、三大框架"值 vs 类型"对照表

记不住哪些是值哪些是类型?看这张表就够了。

Vue3(来自 vue

名称是值还是类型import 方式原因
ref🔵 值(函数)import { ref }运行时调用 ref(0)
Ref🟠 类型import type { Ref }写在 const x: Ref<number>
computed🔵 值import { computed }运行时调用 computed(() => ...)
ComputedRef🟠 类型import type { ComputedRef }写在 const x: ComputedRef<number>
watch🔵 值import { watch }运行时调用 watch(...)
WatchStopHandle🟠 类型import type { WatchStopHandle }写在 const stop: WatchStopHandle
reactive🔵 值import { reactive }运行时调用 reactive({})
MaybeRef🟠 类型import type { MaybeRef }写在参数 price: MaybeRef<number>
InjectionKey🟠 类型import type { InjectionKey }写在 const KEY: InjectionKey<T>
Component🟠 类型import type { Component }写在 const comp: Component
VNode🟠 类型import type { VNode }写在返回类型 (): VNode
h🔵 值import { h }运行时调用 h('div', ...)
defineProps🔵 值(宏)无需导入编译宏,自动可用
ExtractPropTypes🟠 类型import type { ExtractPropTypes }工具类型

记忆规律:Vue3 中小写开头的是值(ref、watch),大写开头的是类型(Ref、WatchStopHandle)。defineProps/defineEmits 是编译宏,不需要导入。


React(来自 react

名称是值还是类型import 方式原因
useState🔵 值import { useState }运行时 Hook
useEffect🔵 值import { useEffect }运行时 Hook
useRef🔵 值import { useRef }运行时 Hook
createContext🔵 值import { createContext }运行时创建 Context
FC🟠 类型import type { FC }FunctionComponent 的别名
ReactNode🟠 类型import type { ReactNode }渲染内容类型
CSSProperties🟠 类型import type { CSSProperties }内联样式类型
ChangeEvent🟠 类型import type { ChangeEvent }事件类型
MouseEvent🟠 类型import type { MouseEvent }事件类型
KeyboardEvent🟠 类型import type { KeyboardEvent }事件类型
FormEvent🟠 类型import type { FormEvent }事件类型
SetStateAction🟠 类型import type { SetStateAction }useState setter 类型
Dispatch🟠 类型import type { Dispatch }useReducer dispatch 类型
RefObject🟠 类型import type { RefObject }useRef 返回值类型
ComponentProps🟠 类型import type { ComponentProps }提取 Props 工具类型
forwardRef🔵 值import { forwardRef }运行时包装组件
memo🔵 值import { memo }运行时包装组件
lazy🔵 值import { lazy }运行时懒加载
Suspense🔵 值import { Suspense }运行时组件

记忆规律:React 中 Hook(useXxx)和组件包装函数 是值,事件类型(XxxEvent)、组件标注类型(FC)、工具类型(ComponentProps) 是类型。


Express(来自 express@types/express

名称是值还是类型import 方式原因
express🔵 值(函数)import express from 'express'运行时创建 app
Router🔵 值(函数)import { Router } from 'express'运行时 Router()
Request🟠 类型import type { Request } from 'express'写在 req: Request<...>
Response🟠 类型import type { Response } from 'express'写在 res: Response<...>
NextFunction🟠 类型import type { NextFunction } from 'express'写在 next: NextFunction
RequestHandler🟠 类型import type { RequestHandler } from 'express'标注中间件
ErrorRequestHandler🟠 类型import type { ErrorRequestHandler } from 'express'标注错误中间件
CookieOptions🟠 类型import type { CookieOptions } from 'express'标注 cookie 配置
json🔵 值import { json } from 'express'app.use(json())

记忆规律:Express 中 Router、express()、中间件工厂函数 是值,Request、Response、NextFunction、各种 Handler 是类型。


四、不用死记,IDE 会告诉你!

方法1:鼠标悬停看提示

import {          // ← 在这里输入时,IDE 自动补全列表
//       ↑ 悬停在补全项上,VSCode 会显示它的定义:
//       "interface Ref<T>" → 是类型 → 用 import type
//       "function ref<T>(value: T): Ref<T>" → 是函数 → 用 import

方法2:看名字规则(90% 准确)

名字特征大概率是举例
小写开头🔵 值ref, watch, useState, express
大写开头🟠 类型Ref, FC, ReactNode, Request
Props 结尾🟠 类型ButtonProps(自定义的)
Event 结尾🟠 类型ChangeEvent, MouseEvent
Type 结尾🟠 类型ComponentType, PropType
Hook(useXxx🔵 值useState, useRef, useAuth
编译宏无需导入defineProps, defineEmits, defineModel

方法3:看导入来源的包名

// 从这些包导入的 → 几乎都是类型
import type { ... } from 'vue'          // Vue 的类型
import type { ... } from 'react'        // React 的类型
import type { ... } from 'express'      // Express 的类型(@types/express)

// 从这些路径导入的 → 大概率是类型
import type { User } from '@/types/user'
import type { ApiResponse } from '@/types/api'

五、混合导入的推荐写法

// ✅ 推荐:值用 import,类型用 import type,分行写
import { ref, watch, computed } from 'vue'
import type { Ref, ComputedRef, MaybeRef } from 'vue'

// ✅ 也可以:同源混合一行
import { ref, type Ref } from 'vue'
//        ↑ 值   ↑ 类型

// ✅ 也可以:值一行,类型一行(react)
import { useState, useEffect } from 'react'
import type { FC, ReactNode, ChangeEvent } from 'react'

// ❌ 不推荐:类型也用普通 import(虽然能跑,但不规范)
import { FC, ReactNode } from 'react'  // 这些是类型,应该用 import type

六、常见误区

误区1:以为 import type 会影响运行时

import type { Ref } from 'vue'
// 编译后的 JS:这行直接消失!
// 所以不用担心"导入类型会增加打包体积"

误区2:把函数当类型导入

import type { ref } from 'vue'
// ❌ 错误!ref 是函数不是类型
const count = ref(0)  // 运行时报错:ref is not defined

误区3:不知道可以混合一行

// 很多人写成这样(两行):
import { ref } from 'vue'
import type { Ref } from 'vue'

// 其实可以一行搞定:
import { ref, type Ref } from 'vue'

一句话总结import type 就是告诉 TS "这个东西只在写类型时用,编译后不用保留"。它的好处是:减少打包体积、避免循环引用、让代码意图更清晰


📖 第十章:tsconfig.json——新手开箱即用配置

{
  "compilerOptions": {
    // 目标版本:Node 用 ESNext,浏览器用 ES2020
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",

    // 严格模式:强烈建议开启!
    "strict": true,

    // JSX 支持(React 用 react-jsx,Vue 不需要)
    "jsx": "react-jsx",

    // 路径别名
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },

    // 其他推荐配置
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    // 输出配置
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

不同框架的关键区别

框架moduleResolutionjsx特殊配置
Vue3bundler不需要"types": ["vue"]
Reactbundlerreact-jsx需要 "jsxImportSource"
Next.jsbundlerpreserveNext 自动管理
Node.jsNodeNext不需要"module": "NodeNext"

📖 第十一章:新手避坑指南

坑1:把 any 当万能药

// ❌ 错误
const data: any = await fetch('/api/users');
data.forEach(user => console.log(user.name)); // 没有提示,运行报错

// ✅ 正确
interface User { id: number; name: string; }
const data: User[] = await fetch('/api/users').then(r => r.json());
data.forEach(user => console.log(user.name)); // 有完整提示

坑2:类型断言滥用

// ❌ 错误:强制类型断言可能掩盖 bug
const el = document.getElementById('app') as HTMLDivElement;
el.innerHTML = '<h1>Hello</h1>'; // 如果元素不存在,运行时报错

// ✅ 正确:先检查再使用
const el = document.getElementById('app');
if (el) {
  el.innerHTML = '<h1>Hello</h1>';
}

坑3:as const 不会用

// ❌ 没有 as const
const colors = ['red', 'green', 'blue']; // 类型是 string[]
// colors[0] 的类型是 string

// ✅ 用了 as const
const colors = ['red', 'green', 'blue'] as const; // 类型是 readonly ["red", "green", "blue"]
// colors[0] 的类型是 "red" 
// [number] 是 TypeScript 的索引访问类型(Indexed Access Type)。对于数组/元组类型,T[number] 表示"用任意数字索引去取值,把所有可能的返回值合并成联合类型"。
type Color = typeof colors[number]; // "red" | "green" | "blue"

坑4:忘记处理 null/undefined

// ❌ 没有处理可能的 null
const user = users.find(u => u.id === 1);
console.log(user.name); // ❌ 可能报错,user 可能是 undefined

// ✅ 正确:先判断
const user = users.find(u => u.id === 1);
if (user) {
  console.log(user.name);
}
// 或者
console.log(user?.name);

坑5:过度设计类型

// ❌ 过度设计:给每个变量都写类型
let count: number = 0;
let name: string = "hello";
let items: Array<string> = ["a", "b"];

// ✅ 简单就好:让 TS 推断
let count = 0;       // TS 推断为 number
let name = "hello";  // TS 推断为 string
let items = ["a", "b"]; // TS 推断为 string[]

📖 第十二章:学习路线图——7天速成计划

Day 1-2:基础类型

  • ✅ 掌握 string, number, boolean, null, undefined
  • ✅ 掌握数组 T[]、对象 { key: Type }
  • ✅ 理解类型推断,知道什么时候该写类型

Day 3-4:函数和接口

  • ✅ 函数参数和返回值的类型标注
  • interfacetype 的区别和选择
  • ✅ 可选属性 ?、只读 readonly

Day 5-6:泛型和工具类型

  • ✅ 理解泛型就是"类型的参数"
  • ✅ 掌握 Partial, Pick, Omit, Record
  • ✅ 在 Vue/React 组件中使用泛型

Day 7:实战整合

  • ✅ 在你的项目中配置 tsconfig.json
  • ✅ 把项目中 3 个 .js 文件改成 .ts
  • ✅ 理解报错信息,学会看 TS 的类型提示

📖 附录:常用速查表

类型操作符速查

操作符含义示例
|联合(或)string | number
&交叉(且)A & B
?可选name?: string
readonly只读readonly id: number
typeof获取值的类型typeof window
keyof获取键的联合类型keyof User
extends约束/继承T extends User
as类型断言x as string
is类型谓词arg is User
infer推断类型变量T extends Promise<infer U>

三句话总结 TypeScript

  1. 类型标注:给变量、函数参数、返回值标明类型,让错误在编码阶段暴露
  2. 类型推断:大多数时候 TS 能自己推断,你只需要在"进口"和"出口"写类型
  3. 泛型:给类型加参数,让你写的代码既能复用又保持类型安全

📖 附录二:typeof 与 Awaited 深度解析

一、typeof 的双重身份——TS 新手最容易混淆的概念

typeof 在 TypeScript 中有两种完全不同的含义,取决于它出现在哪里:

身份1:JavaScript 运行时 typeof(返回字符串)

// 这是你熟悉的 JS typeof,运行后得到一个字符串
typeof 42          // → "number"
typeof "hello"     // → "string"
typeof true        // → "boolean"
typeof undefined   // → "undefined"
typeof null        // → "object"(JS 的历史 bug)
typeof {}          // → "object"
typeof []          // → "object"
typeof (() => {})  // → "function"

特点:出现在 ifconsole.log、变量赋值等执行语句中。

身份2:TypeScript 类型上下文 typeof(返回类型)

// 这是 TS 特有的 typeof,用于获取某个值的类型
const user = { name: "张三", age: 25 };

// 类型上下文中:typeof user → { name: string; age: number }
type UserType = typeof user;

// 现在 UserType 等同于:
// type UserType = { name: string; age: number }

// 可以像普通类型一样使用
const anotherUser: UserType = { name: "李四", age: 30 }; // ✅

特点:出现在 type X = ...interface extends ...、泛型参数等类型定义中。

一图分清两种身份

┌─────────────────────────────────────────────────────┐
│                   typeof 出现在哪?                    │
├──────────────────────┬──────────────────────────────┤
│   执行语句/表达式      │   类型定义/类型注解             │
│   (运行时)            │   (编译时)                    │
├──────────────────────┼──────────────────────────────┤
│ const t = typeof x   │ type T = typeof x            │
│ if (typeof x === "") │ ReturnType<typeof fn>         │
│ console.log(typeof)  │ const obj: typeof someValue   │
├──────────────────────┼──────────────────────────────┤
│ 返回:字符串字面量     │ 返回:TS 类型                  │
│ "string"|"number"..  │ 如 { name: string }           │
└──────────────────────┴──────────────────────────────┘

实际代码中的对比

const user = { name: "张三", age: 25 };

// ===== 运行时 typeof(JS 行为)=====
const result = typeof user;          // result 是字符串 "object"
console.log(typeof user.age);        // 打印 "number"

// ===== 类型上下文 typeof(TS 行为)=====
type UserShape = typeof user;        // UserShape = { name: string; age: number }
const newUser: typeof user = { name: "李四", age: 30 }; // 类型检查

// ===== 两个可以同时出现 =====
if (typeof user.age === "number") {   // ← 运行时 typeof,检查值
  const age: typeof user.age = 25;    // ← 类型 typeof,获取 number 类型
}

typeof 最实用的三个场景

// 场景1:获取变量的类型(不用重复定义 interface)
const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3,
};
type Config = typeof config;
// Config = { apiUrl: string; timeout: number; retries: number }

// 场景2:配合 ReturnType 获取函数返回值类型
function fetchUser() {
  return { id: 1, name: "张三", email: "zhang@example.com" };
}
type User = ReturnType<typeof fetchUser>;
// User = { id: number; name: string; email: string }

// 场景3:从常量数组提取联合类型
const STATUSES = ["idle", "loading", "success", "error"] as const;
type Status = typeof STATUSES[number];
// Status = "idle" | "loading" | "success" | "error"

// 对比:如果不用 typeof,你得手动维护两份
const STATUSES = ["idle", "loading", "success", "error"] as const;
type Status = "idle" | "loading" | "success" | "error"; // ← 容易不同步!

二、Awaited——剥开 Promise 的"洋葱皮"

为什么需要 Awaited?

// 问题场景:你写了一个 async 函数
async function getUser(): Promise<{ id: number; name: string }> {
  return { id: 1, name: "张三" };
}

// 想获取它的返回值类型
type UserReturn = ReturnType<typeof getUser>;
// UserReturn = Promise<{ id: number; name: string }>  ← 得到的是 Promise!
// 但我们通常想要的是 { id: number; name: string }

// 解决方案:用 Awaited 剥掉 Promise 外壳
type ActualUser = Awaited<ReturnType<typeof getUser>>;
// ActualUser = { id: number; name: string }  ← 这才是我要的!

Awaited 的"剥洋葱"能力

// Awaited 会递归剥掉所有嵌套的 Promise
type A = Awaited<Promise<string>>;              // → string
type B = Awaited<Promise<Promise<number>>>;      // → number(两层全剥掉)
type C = Awaited<Promise<Promise<Promise<boolean>>>>; // → boolean(三层也剥掉)

// 联合类型中的 Promise 也会被处理
type D = Awaited<string | Promise<number>>;      // → string | number

// 非 Promise 类型保持不变
type E = Awaited<string>;                        // → string
type F = Awaited<number>;                        // → number

Awaited 实战:四个经典场景

// ===== 场景1:从 async 函数提取纯数据类型 =====
async function fetchTodos(): Promise<Todo[]> {
  const res = await fetch('/api/todos');
  return res.json();
}
type TodoList = Awaited<ReturnType<typeof fetchTodos>>; // Todo[]

// ===== 场景2:从第三方库的函数提取数据类型 =====
// 假设 axios 的 get 返回 Promise<AxiosResponse<User>>
import axios from 'axios';
type UserFromApi = Awaited<ReturnType<typeof axios.get<User>>>;
// 得到 AxiosResponse<User>,包含 data、status、headers 等

// ===== 场景3:从 Pinia Store action 提取返回类型 =====
const useUserStore = defineStore('user', {
  actions: {
    async fetchUser(id: number) {
      return await api.getUser(id); // 返回 User
    }
  }
});
type FetchUserReturn = Awaited<ReturnType<typeof useUserStore.prototype.fetchUser>>;

// ===== 场景4:React 中从 loader 函数提取类型 =====
// (React Router v6+)
async function todoLoader({ params }: LoaderFunctionArgs) {
  return fetchTodo(params.id);
}
type LoaderData = Awaited<ReturnType<typeof todoLoader>>; // Todo 类型

typeof + ReturnType + Awaited 三件套拆解

// 回到你最困惑的那行代码:
type TodosResponse = Awaited<ReturnType<typeof getTodos>>;

// 把它拆成三步走:

// Step 1: typeof getTodos
// 把"值"翻译成"类型"
// 值 getTodos → 类型 () => Promise<Todo[]>

// Step 2: ReturnType<...>
// 提取函数的返回值类型
// ReturnType<() => Promise<Todo[]>> → Promise<Todo[]>

// Step 3: Awaited<...>
// 剥掉 Promise 外壳
// Awaited<Promise<Todo[]>> → Todo[]

// 最终:TodosResponse = Todo[] ✅

记忆口诀

typeof  → "值的身份证"    把值变成类型
ReturnType → "函数吐出什么"  提取函数返回类型  
Awaited → "剥洋葱"       剥掉 Promise 层

最后的话:TypeScript 不是一门新语言,它就是 JavaScript + 类型。不要试图一次性学完所有特性,先把上面这些用熟,你就已经超过了 80% 的 TS 开发者。剩下的 20% 高级特性,边用边学就行。

记住:写 TS 的目标不是"让编译器开心",而是"让你的队友(和三个月后的你)能看懂代码"