基于 TypeScript 官方文档 (v5.x, 2026年6月) 提炼
适用框架:Vue3 · React · Node.js · Next.js · Nuxt · Express
90% 的人学 TS 会犯的错:
死记类型语法 ,而不是理解"为什么要这么写"一次性学太多 ,从 enum 到 namespace 全灌进去,大脑爆炸脱离项目实战 ,学了也不知道用在哪这本手册的目标是:让你用最少的知识,在最多的地方能用 。
JavaScript = 你开车上路,撞了才知道哪里不对
TypeScript = 车上装了导航 + 行车记录仪,出发前就告诉你这条路有问题
TypeScript 不改变 JS 运行时的行为,它只在你写代码的时候 检查类型。编译后,所有类型标注都会被擦除,变成纯 JS。
TypeScript = JavaScript + 类型标注
你所有学的 TS 语法,本质都是在做一件事:告诉编译器"这个变量是什么类型" 。
// 方式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 ;
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 ()) ;
标注参数类型 标注返回类型
↓ ↓
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非原始类型 较少使用
// 方式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 " ;
}
记住一句话:能用 interface 就用 interface,不行再换 type。
场景 用哪个 原因 定义对象形状 interface可扩展,性能更好 联合类型 typeinterface 不支持 元组类型 typeinterface 不支持 需要声明合并 interface同名 interface 自动合并 映射类型 typeinterface 不支持
// 定义数据模型 → 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 ;
// 1. 参数类型
// 2. 返回值类型
// 3. 可选参数(用 ? 标记)
function fetchUser (
id : number , // 必填
token ?: string , // 可选
options : RequestOptions = {} // 有默认值
): Promise < User > { // 返回 Promise<User>
return api . get ( ` /users/ ${ id }` ) ;
}
// 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 >;
}
// 同一个函数,不同参数返回不同类型
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 ) ;
}
泛型 = 类型参数 = 给类型加了个"占位符"
就像函数参数一样:
function add(a, b) → a 和 b 是值的参数
function foo<T>(a: T) → T 是类型的参数
// 问题:这个函数返回值和输入类型一样,但 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
// 要求 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
// ---- 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 >;
}
当你有一个联合类型 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 )) ;
}
}
// 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 " ;
}
// 这是你在 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 ;
}
}
不用死记硬背,把这张表放在手边,用到时查:
工具类型 作用 一句话口诀 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 表示这个类型是在哪定义的,方便你追溯。
// 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 的可选属性设置默认值
// ===== 使用场景:父组件给子组件传数据 =====
// 父组件:<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
// ===== 使用场景:子组件通知父组件"发生了什么" =====
// 父组件:<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 类型,代表表单数据
// ===== 使用场景:在模板中获取 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 暴露的方法都有类型提示
// ===== 使用场景:封装可复用的数据请求逻辑 =====
// 这个 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 有完整代码提示!
// ===== 使用场景:管理购物车的全局状态 =====
// 先定义数据类型(通常放在 @/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
每个类型都标注了来源,附带使用场景和代码示例。
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.$http、this.$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 跨组件安全传值。
// React 的核心类型来自这些导入
import { useState , useRef , createContext , useContext } from ' react ' ;
// ↑ ↑ ↑ ↑
// React 包的 Hooks 和 API(从 'react' 导入)
import type { FC , ReactNode , ChangeEvent , FormEvent , MouseEvent } from ' react ' ;
// ↑ ↑ ↑ ↑ ↑
// React 的类型定义(用 type 关键字只导入类型,编译后不产生代码)
// ===== 使用场景:定义一个可复用的按钮组件 =====
// 父组件:<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 >;
};
// ===== 使用场景:组件内部的状态管理 =====
// 先定义数据类型(通常放在 @/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") → ❌ 类型错误,只能传这三个值之一
// ===== 使用场景:获取 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" → ❌ 类型错误
// ===== 使用场景:处理 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>
// ===== 使用场景:封装 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
// ===== 使用场景:全局共享登录状态 =====
// 文件:@/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 包。
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 管共享。
// 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 中
// ===== 使用场景:在 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
} ) ;
// ===== 使用场景: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
// ===== 使用场景:给 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 ✅
// ===== 使用场景:写一个 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; // ✅ 有类型提示
// });
// ===== 使用场景:统一捕获 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 内置类型来自 @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 风格的回调错误类型,带 errno、code、syscall 等属性。
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/node(buffer 模块)
二进制数据缓冲区类型。
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/node(url 模块)
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/node(stream 模块)
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/node(events 模块)
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 自动生成。
// 方式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 中 ✅ 保留 ❌ 完全擦除 循环引用 可能出问题 ✅ 安全
黄金法则 :问自己——"这个东西运行时存在吗?"
// ✅ 这些是函数,运行时存在 → 普通 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。
记不住哪些是值哪些是类型?看这张表就够了。
名称 是值还是类型 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 是编译宏,不需要导入。
名称 是值还是类型 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) 是类型。
名称 是值还是类型 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 是类型。
方法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, PropTypeHook(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 "这个东西只在写类型时用,编译后不用保留"。它的好处是:减少打包体积、避免循环引用、让代码意图更清晰 。
{
" 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 " ]
}
框架 moduleResolution jsx 特殊配置 Vue3 bundler不需要 "types": ["vue"]React bundlerreact-jsx需要 "jsxImportSource" Next.js bundlerpreserveNext 自动管理 Node.js NodeNext不需要 "module": "NodeNext"
// ❌ 错误
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)) ; // 有完整提示
// ❌ 错误:强制类型断言可能掩盖 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> ' ;
}
// ❌ 没有 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"
// ❌ 没有处理可能的 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) ;
// ❌ 过度设计:给每个变量都写类型
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[]
✅ 掌握 string, number, boolean, null, undefined ✅ 掌握数组 T[]、对象 { key: Type } ✅ 理解类型推断,知道什么时候该写类型 ✅ 函数参数和返回值的类型标注 ✅ interface 和 type 的区别和选择 ✅ 可选属性 ?、只读 readonly ✅ 理解泛型就是"类型的参数" ✅ 掌握 Partial, Pick, Omit, Record ✅ 在 Vue/React 组件中使用泛型 ✅ 在你的项目中配置 tsconfig.json ✅ 把项目中 3 个 .js 文件改成 .ts ✅ 理解报错信息,学会看 TS 的类型提示 操作符 含义 示例 |联合(或) string | number&交叉(且) A & B?可选 name?: stringreadonly只读 readonly id: numbertypeof获取值的类型 typeof windowkeyof获取键的联合类型 keyof Userextends约束/继承 T extends Useras类型断言 x as stringis类型谓词 arg is Userinfer推断类型变量 T extends Promise<infer U>
类型标注 :给变量、函数参数、返回值标明类型,让错误在编码阶段暴露类型推断 :大多数时候 TS 能自己推断,你只需要在"进口"和"出口"写类型泛型 :给类型加参数,让你写的代码既能复用又保持类型安全typeof 在 TypeScript 中有两种完全不同的含义 ,取决于它出现在哪里:
// 这是你熟悉的 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"
特点 :出现在 if、console.log、变量赋值等执行语句 中。
// 这是 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 类型
}
// 场景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 " ; // ← 容易不同步!
// 问题场景:你写了一个 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 会递归剥掉所有嵌套的 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
// ===== 场景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 类型
// 回到你最困惑的那行代码:
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 的目标不是"让编译器开心",而是"让你的队友(和三个月后的你)能看懂代码" 。