🚀 全新升级 - Nuxt4 AI企业级开发实战指南现已发布!

TypeScript 集成

掌握TypeScript在Nuxt3中的深度集成,提升代码质量和开发体验,构建类型安全的企业级应用

🎯 TypeScript 概览

TypeScript 是JavaScript的超集,为动态语言JavaScript添加了静态类型检查。在Nuxt3中,TypeScript享有一流的支持,提供了完整的类型推导、智能提示和错误检查,大大提升了企业级应用的开发体验和代码质量。

🚀 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"
  ]
}

高级配置

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;
};

高级类型模式

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);
}

🎨 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>

组合式函数类型化

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
  };
}

🔧 服务器端类型化

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; // 示例
}

中间件类型化

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'
    });
  }
});

🎯 最佳实践

常见问题和解决方案

类型断言和保护
工具类型的使用
// ❌ 避免使用 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);
}

🚀 下一步学习

掌握了TypeScript集成后,建议继续学习:

开发工作流

学习现代化的开发工作流和工具链