Vue 3 实战:从页面到接口调用

Vue 3 实战:从页面到接口调用

前端最重要的工作之一是和后端接口交互。这篇说说 Vue 项目中怎么发请求、封装 API、管理错误。

安装 axios

pnpm add axios

封装请求工具

创建 src/utils/request.ts

import axios from 'axios'
import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios'

const request: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 10000
})

// 请求拦截
request.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 添加 token
    const token = localStorage.getItem('token')
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error: AxiosError) => {
    return Promise.reject(error)
  }
)

// 响应拦截
request.interceptors.response.use(
  (response: AxiosResponse) => {
    return response.data
  },
  (error: AxiosError) => {
    // 统一错误处理
    if (error.response?.status === 401) {
      localStorage.removeItem('token')
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export default request

封装 API 模块

创建 src/api/user.ts

import request from '../utils/request'

export interface User {
  id: number
  name: string
  email: string
}

export const userApi = {
  list(params?: { page: number; size: number }) {
    return request.get<{ list: User[]; total: number }>('/users', { params })
  },

  get(id: number) {
    return request.get<User>(`/users/${id}`)
  },

  create(data: Partial<User>) {
    return request.post<User>('/users', data)
  },

  update(id: number, data: Partial<User>) {
    return request.put<User>(`/users/${id}`, data)
  },

  delete(id: number) {
    return request.delete(`/users/${id}`)
  }
}

在组件中使用

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { userApi } from '../api/user'
import type { User } from '../api/user'

const users = ref<User[]>([])
const loading = ref(false)
const error = ref<string | null>(null)

async function fetchUsers() {
  loading.value = true
  error.value = null
  try {
    const res = await userApi.list({ page: 1, size: 10 })
    users.value = res.list
  } catch (e) {
    error.value = '获取用户列表失败'
  } finally {
    loading.value = false
  }
}

async function handleDelete(id: number) {
  try {
    await userApi.delete(id)
    await fetchUsers() // 刷新列表
  } catch {
    alert('删除失败')
  }
}

onMounted(fetchUsers)
</script>

<template>
  <div>
    <p v-if="loading">加载中...</p>
    <p v-else-if="error">{{ error }}</p>
    <ul v-else>
      <li v-for="user in users" :key="user.id">
        {{ user.name }} - {{ user.email }}
        <button @click="handleDelete(user.id)">删除</button>
      </li>
    </ul>
  </div>
</template>

封装组合式函数

把请求逻辑抽成可复用函数,创建 src/composables/useUser.ts

import { ref } from 'vue'
import { userApi } from '../api/user'
import type { User } from '../api/user'

export function useUser() {
  const users = ref<User[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  async function fetchUsers() {
    loading.value = true
    error.value = null
    try {
      const res = await userApi.list({ page: 1, size: 10 })
      users.value = res.list
    } catch (e) {
      error.value = '获取用户列表失败'
    } finally {
      loading.value = false
    }
  }

  async function createUser(data: Partial<User>) {
    await userApi.create(data)
    await fetchUsers()
  }

  async function deleteUser(id: number) {
    await userApi.delete(id)
    await fetchUsers()
  }

  return {
    users,
    loading,
    error,
    fetchUsers,
    createUser,
    deleteUser
  }
}

组件中使用更简洁:

<script setup lang="ts">
import { onMounted } from 'vue'
import { useUser } from '../composables/useUser'

const { users, loading, error, fetchUsers, deleteUser } = useUser()

onMounted(fetchUsers)
</script>

环境变量

创建 .env.development.env.production

# .env.development
VITE_API_BASE_URL=http://localhost:3000/api

# .env.production
VITE_API_BASE_URL=https://api.example.com

使用:import.meta.env.VITE_API_BASE_URL

总结

  • axios 封装请求工具,统一处理拦截和错误
  • API 模块化,按功能拆分
  • 组合式函数封装复用逻辑
  • 环境变量管理不同环境的配置

下篇是整个系列的最后一篇,做一个完整的列表页增删改查实战。

最后更新 4/30/2026, 8:57:45 AM