TypeScript 集成
掌握TypeScript在Nuxt3中的深度集成,提升代码质量和开发体验,构建类型安全的企业级应用
🎯 TypeScript 概览
TypeScript 是JavaScript的超集,为动态语言JavaScript添加了静态类型检查。在Nuxt3中,TypeScript享有一流的支持,提供了完整的类型推导、智能提示和错误检查,大大提升了企业级应用的开发体验和代码质量。
为什么在Nuxt3中使用TypeScript?
- 🔍 类型安全: 编译时发现潜在错误,减少运行时bug
- 💡 智能提示: IDE提供更好的代码补全和导航
- 📚 更好的文档: 类型即文档,提升代码可读性
- 🔧 重构支持: 安全的代码重构和修改
- 🏢 团队协作: 统一的接口规范,降低沟通成本
🚀 TypeScript 配置
基础配置
Nuxt3默认支持TypeScript,只需创建或修改配置文件:
tsconfig.json
nuxt.config.ts
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
// 基础配置
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
// 模块解析
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
// 路径映射
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"~~/*": ["./*"],
"@@/*": ["./*"]
},
// 类型检查
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// 输出配置
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.vue"
],
"exclude": [
"node_modules",
".nuxt",
".output",
"dist"
]
}
export default defineNuxtConfig({
// TypeScript配置
typescript: {
strict: true,
typeCheck: true
},
// 构建时类型检查
hooks: {
'build:before': () => {
console.log('开始TypeScript类型检查...');
}
},
// Vite配置
vite: {
vue: {
script: {
defineModel: true,
propsDestructure: true
}
}
}
});
高级配置
tsconfig.json - 企业级配置
{
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
// 严格模式
"strict": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUncheckedSideEffectImports": true,
// 实验性功能
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// 路径映射 (企业级)
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/composables/*": ["./composables/*"],
"@/utils/*": ["./utils/*"],
"@/types/*": ["./types/*"],
"@/stores/*": ["./stores/*"],
"@/server/*": ["./server/*"],
"@/assets/*": ["./assets/*"]
},
// 插件支持
"plugins": [
{
"name": "@vue/typescript-plugin"
}
]
},
// 项目引用 (Monorepo支持)
"references": [
{ "path": "./packages/shared" },
{ "path": "./packages/ui" }
]
}
🧩 核心类型定义
基础类型
types/basic.ts
types/ai.ts
// 用户相关类型
export interface User {
readonly id: number;
name: string;
email: string;
avatar?: string;
role: UserRole;
createdAt: Date;
updatedAt: Date;
}
export type UserRole = 'admin' | 'user' | 'guest';
export type CreateUserPayload = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
export type UpdateUserPayload = Partial<Pick<User, 'name' | 'email' | 'avatar'>>;
// API响应类型
export interface ApiResponse<T = any> {
success: boolean;
data: T;
message?: string;
errors?: Record<string, string[]>;
}
export interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
perPage: number;
totalPages: number;
};
}
// 状态类型
export interface LoadingState {
isLoading: boolean;
error: string | null;
}
export type AsyncState<T> = LoadingState & {
data: T | null;
};
// AI相关类型
export interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
metadata?: MessageMetadata;
}
export interface MessageMetadata {
model?: string;
temperature?: number;
tokens?: number;
cost?: number;
}
export interface AIGenerateOptions {
model: 'gpt-4' | 'gpt-3.5-turbo' | 'claude-3-haiku' | 'claude-3-sonnet';
temperature?: number;
maxTokens?: number;
systemPrompt?: string;
stream?: boolean;
}
export interface AIProvider {
name: string;
models: string[];
generateText: (prompt: string, options?: AIGenerateOptions) => Promise<string>;
generateStream: (prompt: string, options?: AIGenerateOptions) => AsyncIterable<string>;
}
// 泛型约束
export interface WithTimestamps {
createdAt: Date;
updatedAt: Date;
}
export type Entity<T = {}> = T & WithTimestamps & {
id: number;
};
高级类型模式
types/advanced.ts
types/api.ts
// 条件类型
export type NonNullable<T> = T extends null | undefined ? never : T;
export type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
// 模板字面量类型
export type ApiEndpoint<T extends string> = `/api/${T}`;
export type EventName<T extends string> = `on${Capitalize<T>}`;
// 示例使用
type UserEndpoints = ApiEndpoint<'users' | 'auth' | 'profile'>;
// 结果: "/api/users" | "/api/auth" | "/api/profile"
type Events = EventName<'click' | 'change' | 'submit'>;
// 结果: "onClick" | "onChange" | "onSubmit"
// 映射类型
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type RequiredBy<T, K extends keyof T> = T & Required<Pick<T, K>>;
// 递归类型
export interface TreeNode<T = any> {
id: string;
data: T;
children?: TreeNode<T>[];
parent?: TreeNode<T>;
}
// 工具类型
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
};
export type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
};
// 品牌类型 (Branded Types)
declare const __brand: unique symbol;
type Brand<T, B> = T & { [__brand]: B };
export type UserId = Brand<number, 'UserId'>;
export type Email = Brand<string, 'Email'>;
export type JWT = Brand<string, 'JWT'>;
// 类型保护
export function isUserId(value: number): value is UserId {
return typeof value === 'number' && value > 0;
}
export function isEmail(value: string): value is Email {
return /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(value);
}
// API类型系统
export interface ApiClient {
get: <T>(url: string, config?: RequestConfig) => Promise<T>;
post: <T>(url: string, data?: any, config?: RequestConfig) => Promise<T>;
put: <T>(url: string, data?: any, config?: RequestConfig) => Promise<T>;
delete: <T>(url: string, config?: RequestConfig) => Promise<T>;
}
export interface RequestConfig {
headers?: Record<string, string>;
timeout?: number;
retries?: number;
}
// RESTful API类型
export interface RestfulApi<T, CreateT = Omit<T, 'id'>, UpdateT = Partial<T>> {
list: (params?: ListParams) => Promise<PaginatedResponse<T>>;
get: (id: number) => Promise<T>;
create: (data: CreateT) => Promise<T>;
update: (id: number, data: UpdateT) => Promise<T>;
delete: (id: number) => Promise<void>;
}
export interface ListParams {
page?: number;
limit?: number;
sort?: string;
order?: 'asc' | 'desc';
search?: string;
filters?: Record<string, any>;
}
// 具体API实现类型
export type UserApi = RestfulApi<User, CreateUserPayload, UpdateUserPayload>;
export interface AuthApi {
login: (credentials: LoginCredentials) => Promise<AuthResponse>;
register: (userData: RegisterPayload) => Promise<User>;
logout: () => Promise<void>;
refreshToken: () => Promise<AuthResponse>;
me: () => Promise<User>;
}
export interface AuthResponse {
user: User;
token: string;
refreshToken: string;
expiresIn: number;
}
🎨 Vue组件类型化
组件Props和Emits
components/UserCard.vue
components/DataTable.vue
<template>
<div class="user-card">
<img :src="user.avatar" :alt="user.name">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<UiButton
:variant="buttonVariant"
@click="handleEdit"
>
编辑
</UiButton>
</div>
</template>
<script setup lang="ts">
// Props类型定义
interface Props {
user: User;
editable?: boolean;
size?: 'small' | 'medium' | 'large';
variant?: 'default' | 'compact';
}
// 使用泛型默认值
const props = withDefaults(defineProps<Props>(), {
editable: true,
size: 'medium',
variant: 'default'
});
const emit = defineEmits<Emits>();
// Emits类型定义
interface Emits {
edit: [user: User];
delete: [userId: number];
statusChange: [userId: number, status: UserStatus];
}
// 计算属性
const buttonVariant = computed(() => {
return props.size === 'small' ? 'ghost' : 'default';
});
// 方法
function handleEdit() {
emit('edit', props.user);
}
// 类型化的ref
const cardElement = ref<HTMLDivElement>();
// 生命周期
onMounted(() => {
console.log('UserCard mounted for user:', props.user.name);
});
</script>
<template>
<div class="data-table">
<table>
<thead>
<tr>
<th
v-for="column in columns"
:key="column.key"
@click="handleSort(column)"
>
{{ column.label }}
<SortIcon
v-if="column.sortable"
:direction="getSortDirection(column.key)"
/>
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="getItemKey(item)">
<td v-for="column in columns" :key="column.key">
<slot
:name="column.key"
:item="item"
:value="getColumnValue(item, column.key)"
>
{{ getColumnValue(item, column.key) }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts" generic="T extends Record<string, any>">
// 泛型组件类型定义
interface Column<K extends keyof T = keyof T> {
key: K;
label: string;
sortable?: boolean;
width?: string;
align?: 'left' | 'center' | 'right';
}
interface Props<T> {
items: T[];
columns: Column<keyof T>[];
sortBy?: keyof T;
sortOrder?: 'asc' | 'desc';
keyField?: keyof T;
}
interface Emits<T> {
sort: [column: keyof T, order: 'asc' | 'desc'];
rowClick: [item: T];
select: [selectedItems: T[]];
}
const props = withDefaults(defineProps<Props<T>>(), {
keyField: 'id' as keyof T,
sortOrder: 'asc'
});
const emit = defineEmits<Emits<T>>();
// 工具函数
function getItemKey(item: T): string | number {
return item[props.keyField] as string | number;
}
function getColumnValue(item: T, key: keyof T): any {
return item[key];
}
function getSortDirection(key: keyof T): 'asc' | 'desc' | null {
return props.sortBy === key ? props.sortOrder : null;
}
function handleSort(column: Column<keyof T>) {
if (!column.sortable)
return;
const newOrder = props.sortBy === column.key && props.sortOrder === 'asc'
? 'desc'
: 'asc';
emit('sort', column.key, newOrder);
}
</script>
组合式函数类型化
composables/useApi.ts
composables/useForm.ts
// 泛型API组合式函数
export function useApi<T = any>() {
const { $fetch } = useNuxtApp();
// 状态
const data = ref<T | null>(null);
const loading = ref(false);
const error = ref<ApiError | null>(null);
// 请求方法
const request = async <R = T>(
url: string,
options: FetchOptions = {}
): Promise<R> => {
loading.value = true;
error.value = null;
try {
const response = await $fetch<R>(url, options);
data.value = response as unknown as T;
return response;
} catch (err) {
const apiError = err as ApiError;
error.value = apiError;
throw apiError;
} finally {
loading.value = false;
}
};
// CRUD操作
const get = <R = T>(url: string, query?: Record<string, any>): Promise<R> => {
return request<R>(url, { method: 'GET', query });
};
const post = <R = T>(url: string, body?: any): Promise<R> => {
return request<R>(url, { method: 'POST', body });
};
const put = <R = T>(url: string, body?: any): Promise<R> => {
return request<R>(url, { method: 'PUT', body });
};
const del = <R = void>(url: string): Promise<R> => {
return request<R>(url, { method: 'DELETE' });
};
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
request,
get,
post,
put,
delete: del
};
}
// 特定资源的API
export function useUserApi() {
const api = useApi<User>();
const users = ref<User[]>([]);
const currentUser = ref<User | null>(null);
const fetchUsers = async (params?: ListParams): Promise<PaginatedResponse<User>> => {
const response = await api.get<PaginatedResponse<User>>('/api/users', params);
users.value = response.data;
return response;
};
const fetchUser = async (id: UserId): Promise<User> => {
const user = await api.get<User>(`/api/users/${id}`);
currentUser.value = user;
return user;
};
const createUser = async (userData: CreateUserPayload): Promise<User> => {
const newUser = await api.post<User>('/api/users', userData);
users.value.push(newUser);
return newUser;
};
const updateUser = async (id: UserId, userData: UpdateUserPayload): Promise<User> => {
const updatedUser = await api.put<User>(`/api/users/${id}`, userData);
// 更新本地状态
const index = users.value.findIndex(u => u.id === id);
if (index !== -1) {
users.value[index] = updatedUser;
}
if (currentUser.value?.id === id) {
currentUser.value = updatedUser;
}
return updatedUser;
};
return {
...api,
users: readonly(users),
currentUser: readonly(currentUser),
fetchUsers,
fetchUser,
createUser,
updateUser
};
}
// 类型化表单组合式函数
interface FormValidationRule<T = any> {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
email?: boolean;
custom?: (value: T) => string | null;
}
type FormRules<T> = {
[K in keyof T]?: FormValidationRule<T[K]>
};
type FormErrors<T> = {
[K in keyof T]?: string
};
export function useForm<T extends Record<string, any>>(
initialValues: T,
rules?: FormRules<T>
) {
// 表单数据
const form = reactive<T>({ ...initialValues });
const errors = reactive<FormErrors<T>>({} as FormErrors<T>);
const touched = reactive<Record<keyof T, boolean>>({} as Record<keyof T, boolean>);
// 验证单个字段
const validateField = (key: keyof T): boolean => {
const value = form[key];
const rule = rules?.[key];
if (!rule) {
errors[key] = undefined;
return true;
}
// 必填验证
if (rule.required && (!value || (typeof value === 'string' && !value.trim()))) {
errors[key] = '此字段为必填项';
return false;
}
// 长度验证
if (typeof value === 'string') {
if (rule.minLength && value.length < rule.minLength) {
errors[key] = `最少需要 ${rule.minLength} 个字符`;
return false;
}
if (rule.maxLength && value.length > rule.maxLength) {
errors[key] = `最多允许 ${rule.maxLength} 个字符`;
return false;
}
}
// 邮箱验证
if (rule.email && typeof value === 'string') {
const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
if (!emailRegex.test(value)) {
errors[key] = '请输入有效的邮箱地址';
return false;
}
}
// 正则验证
if (rule.pattern && typeof value === 'string') {
if (!rule.pattern.test(value)) {
errors[key] = '格式不正确';
return false;
}
}
// 自定义验证
if (rule.custom) {
const customError = rule.custom(value);
if (customError) {
errors[key] = customError;
return false;
}
}
errors[key] = undefined;
return true;
};
// 验证所有字段
const validate = (): boolean => {
let isValid = true;
for (const key in form) {
const fieldValid = validateField(key);
if (!fieldValid) {
isValid = false;
}
}
return isValid;
};
// 计算属性
const isValid = computed(() => {
return Object.keys(errors).every(key => !errors[key as keyof T]);
});
const isDirty = computed(() => {
return Object.keys(form).some(key =>
JSON.stringify(form[key as keyof T]) !== JSON.stringify(initialValues[key as keyof T])
);
});
// 重置表单
const reset = () => {
Object.assign(form, { ...initialValues });
Object.keys(errors).forEach((key) => {
errors[key as keyof T] = undefined;
});
Object.keys(touched).forEach((key) => {
touched[key as keyof T] = false;
});
};
// 设置字段值
const setFieldValue = <K extends keyof T>(key: K, value: T[K]) => {
form[key] = value;
touched[key] = true;
validateField(key);
};
// 设置字段错误
const setFieldError = <K extends keyof T>(key: K, error: string) => {
errors[key] = error;
};
return {
form,
errors: readonly(errors),
touched: readonly(touched),
isValid,
isDirty,
validateField,
validate,
reset,
setFieldValue,
setFieldError
};
}
🔧 服务器端类型化
API路由类型化
server/api/users/[id].get.ts
server/api/ai/chat.post.ts
import type { ApiResponse, User } from '~/types';
export default defineEventHandler(async (event): Promise<ApiResponse<User>> => {
// 获取路由参数并类型化
const id = getRouterParam(event, 'id');
if (!id || isNaN(Number(id))) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid user ID'
});
}
const userId = Number(id) as UserId;
try {
// 从数据库获取用户
const user = await getUserById(userId);
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'User not found'
});
}
return {
success: true,
data: user
};
} catch (error) {
console.error('Error fetching user:', error);
throw createError({
statusCode: 500,
statusMessage: 'Internal server error'
});
}
});
// 辅助函数
async function getUserById(id: UserId): Promise<User | null> {
// 数据库查询逻辑
// 返回类型化的用户对象
return null; // 示例
}
import type { AIGenerateOptions, ApiResponse, ChatMessage } from '~/types';
import { OpenAI } from 'openai';
interface ChatRequest {
message: string;
conversation: ChatMessage[];
options?: AIGenerateOptions;
}
interface ChatResponse {
response: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
}
export default defineEventHandler(async (event): Promise<ApiResponse<ChatResponse>> => {
// 验证请求体
const body = await readBody<ChatRequest>(event);
if (!body.message?.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Message is required'
});
}
// 获取配置
const config = useRuntimeConfig();
const openai = new OpenAI({
apiKey: config.openaiApiKey
});
try {
// 构建消息历史
const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
...body.conversation.map(msg => ({
role: msg.role,
content: msg.content
})),
{
role: 'user' as const,
content: body.message
}
];
// 调用OpenAI API
const completion = await openai.chat.completions.create({
model: body.options?.model || 'gpt-3.5-turbo',
messages,
temperature: body.options?.temperature || 0.7,
max_tokens: body.options?.maxTokens || 1000
});
const response = completion.choices[0]?.message?.content;
if (!response) {
throw new Error('No response from AI');
}
return {
success: true,
data: {
response,
usage: {
promptTokens: completion.usage?.prompt_tokens || 0,
completionTokens: completion.usage?.completion_tokens || 0,
totalTokens: completion.usage?.total_tokens || 0
}
}
};
} catch (error) {
console.error('AI API error:', error);
throw createError({
statusCode: 500,
statusMessage: 'AI service error'
});
}
});
中间件类型化
server/middleware/auth.ts
import type { User } from '~/types';
import jwt from 'jsonwebtoken';
// 扩展H3事件上下文
declare module 'h3' {
interface H3EventContext {
user?: User;
token?: string;
}
}
export default defineEventHandler(async (event) => {
// 只对API路由进行认证检查
if (!event.node.req.url?.startsWith('/api/')) {
return;
}
// 跳过公开API
const publicRoutes = ['/api/auth/login', '/api/auth/register'];
if (publicRoutes.includes(event.node.req.url)) {
return;
}
// 获取token
const authHeader = getHeader(event, 'authorization');
const token = authHeader?.replace('Bearer ', '') || getCookie(event, 'auth-token');
if (!token) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized'
});
}
try {
// 验证JWT
const config = useRuntimeConfig();
const payload = jwt.verify(token, config.jwtSecret) as { userId: number };
// 获取用户信息
const user = await getUserById(payload.userId as UserId);
if (!user) {
throw new Error('User not found');
}
// 将用户信息添加到上下文
event.context.user = user;
event.context.token = token;
} catch (error) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid token'
});
}
});
🎯 最佳实践
🌟 TypeScript开发建议
- 严格模式: 启用所有严格类型检查选项
- 类型优先: 先定义类型,再编写实现
- 泛型使用: 合理使用泛型提高代码复用性
- 类型保护: 使用类型保护函数确保运行时安全
- 渐进式采用: 在现有项目中逐步引入TypeScript
- 文档化: 使用JSDoc为复杂类型添加文档
常见问题和解决方案
类型断言和保护
工具类型的使用
// ❌ 避免使用 any
const data: any = await $fetch('/api/users');
// ✅ 使用具体类型
const data: User[] = await $fetch<User[]>('/api/users');
// ❌ 强制类型转换
const user = response as User;
// ✅ 使用类型保护
function isUser(obj: any): obj is User {
return obj
&& typeof obj.id === 'number'
&& typeof obj.name === 'string'
&& typeof obj.email === 'string';
}
if (isUser(response)) {
// 这里 response 的类型是 User
console.log(response.name);
}
// ✅ 使用工具类型避免重复
interface CreateUserForm extends Omit<User, 'id' | 'createdAt' | 'updatedAt'> {
confirmPassword: string;
}
// ✅ 条件类型用于API响应
type ApiResult<T, E = ApiError>
= | { success: true; data: T }
| { success: false; error: E };
// ✅ 映射类型用于表单状态
type FormState<T> = {
[K in keyof T]: {
value: T[K];
error?: string;
touched: boolean;
}
};
🚀 下一步学习
掌握了TypeScript集成后,建议继续学习:
开发工作流
学习现代化的开发工作流和工具链