Alova 封装与 Vue 3 集成示例
Alova 封装与 Vue 3 集成示例:打造优雅的 API 请求管理
引言
在 Vue 3 项目中,API 请求管理是开发中的核心环节。Alova 作为一个轻量级、高性能的请求策略库,通过其声明式的 Method
实例和响应式状态管理,为开发者提供了简洁而强大的 API 调用方式。本文将展示如何对 Alova 进行封装,创建一个统一的 request
函数,并在 API 文件中组织请求方法,同时结合 Vue 3 的实际场景提供详细示例代码。目标是通过封装提升代码复用性、简化调用方式,并确保类型安全和可维护性。
为什么封装 Alova?
直接使用 Alova 的 createAlova
和 useRequest
等 API 虽然灵活,但在大型项目中可能导致以下问题:
- 配置重复:每个请求都需要重复定义
baseURL
、拦截器等。 - 代码分散:API 定义分散在组件中,难以统一管理和维护。
- 调用复杂:每次调用都需要创建
Method
实例并使用 Hook。
通过封装 Alova,可以实现:
- 统一配置:集中管理请求配置,如拦截器、错误处理。
- 简洁调用:提供一个
request
函数,隐藏底层细节。 - 模块化 API 管理:将 API 按模块组织在单独文件中,方便复用和维护。
- 类型安全:结合 TypeScript,确保参数和返回数据的类型准确。
封装方案设计
1. 封装 Alova 实例
创建一个全局 Alova 实例,统一配置 baseURL
、请求适配器、拦截器等。
2. 封装 request
函数
设计一个通用的 request
函数,接受 Method
实例并返回响应式状态,简化组件中的调用逻辑。
3. 组织 API 文件
按模块(如用户、待办事项)创建 API 文件,定义 Method
实例,集中管理所有请求。
4. 集成 Vue 3
在 Vue 3 组件中使用封装后的 request
函数,展示多种场景(如基本请求、分页、表单提交)。
代码实现
以下是基于 Vue 3 和 TypeScript 的完整封装示例,包含项目结构和代码。
项目结构
src/
├── api/
│ ├── index.ts # 封装 request 函数和 Alova 实例
│ ├── modules/
│ │ ├── todoApi.ts # 待办事项相关 API
│ │ ├── userApi.ts # 用户相关 API
├── components/
│ ├── TodoList.vue # 待办事项列表组件
│ ├── UserForm.vue # 用户表单提交组件
├── App.vue # 主应用组件
├── main.ts # 入口文件
├── types/
│ ├── api.ts # API 类型定义
1. 封装 Alova 实例与 request
函数 (src/api/index.ts
)
import { createAlova, Method } from 'alova';
import adapterFetch from 'alova/fetch';
import VueHook from 'alova/vue';
import type { Ref } from 'vue';// 定义响应数据结构
interface ResponseData<T> {code: number;message: string;data: T;
}// Alova 实例
export const alovaInstance = createAlova({baseURL: 'https://api.example.com',statesHook: VueHook,requestAdapter: adapterFetch(),// 请求拦截器beforeRequest(method: Method) {const token = localStorage.getItem('token');if (token) {method.config.headers = {...method.config.headers,Authorization: `Bearer ${token}`,};}},// 响应拦截器responded: {onSuccess: async (response) => {const data: ResponseData<any> = await response.json();if (data.code !== 200) {throw new Error(data.message || 'Request failed');}return data.data;},onError: (error) => {console.error('Request error:', error);throw error;},},
});// 封装 request 函数
interface RequestOptions {immediate?: boolean;initialData?: any;
}export function request<T>(method: Method, options: RequestOptions = {}) {const { immediate = true, initialData } = options;const { loading, data, error, send, onSuccess, onError } = useRequest(method, {immediate,initialData,});return {loading: loading as Ref<boolean>,data: data as Ref<T | undefined>,error: error as Ref<Error | undefined>,send,onSuccess,onError,};
}
说明:
alovaInstance
配置了baseURL
、Vue 3 响应式 Hook 和 fetch 适配器。- 请求拦截器添加了认证头,响应拦截器统一处理返回数据格式。
request
函数封装了useRequest
,返回响应式状态和控制方法,简化调用。
2. 定义 API 模块 (src/api/modules/todoApi.ts
)
import { alovaInstance } from '../index';
import type { Todo } from '../../types/api';// 获取待办事项列表
export const getTodoList = (page: number, size: number, search?: string) =>alovaInstance.Get<Todo[]>('/todos', {params: { page, size, search },cacheFor: 60 * 1000, // 缓存 1 分钟transformData: (data: Todo[]) => data,});// 创建待办事项
export const createTodo = (todo: Partial<Todo>) =>alovaInstance.Post<Todo>('/todos', todo);// 删除待办事项
export const deleteTodo = (id: number) =>alovaInstance.Delete<void>(`/todos/${id}`);
说明:
- 每个 API 方法返回一个
Method
实例,包含请求配置。 - 使用 TypeScript 定义返回类型,确保类型安全。
cacheFor
设置缓存,减少重复请求。
3. 定义用户 API 模块 (src/api/modules/userApi.ts
)
import { alovaInstance } from '../index';
import type { User } from '../../types/api';// 用户登录
export const login = (credentials: { username: string; password: string }) =>alovaInstance.Post<{ token: string }>('/login', credentials);// 获取用户信息
export const getUserInfo = () =>alovaInstance.Get<User>('/user', {cacheFor: 5 * 60 * 1000, // 缓存 5 分钟});
4. 类型定义 (src/types/api.ts
)
export interface Todo {id: number;title: string;completed: boolean;
}export interface User {id: number;username: string;email: string;
}
说明:
- 定义数据模型,确保 API 返回数据与组件使用的类型一致。
5. 使用封装的 API:待办事项列表 (src/components/TodoList.vue
)
<script setup lang="ts">
import { ref } from 'vue';
import { request } from '../api';
import { getTodoList, deleteTodo } from '../api/modules/todoApi';
import type { Todo } from '../types/api';const page = ref(1);
const size = ref(10);
const search = ref('');// 使用 request 调用 API
const { loading, data: todos, error } = request<Todo[]>(getTodoList(page.value, size.value, search.value));// 删除待办事项
const handleDelete = async (id: number) => {const { error } = request(deleteTodo(id), { immediate: false });await error.value?.send();if (!error.value) {todos.value = todos.value?.filter((todo) => todo.id !== id);}
};// 分页控制
const nextPage = () => page.value++;
const prevPage = () => page.value > 1 && page.value--;
</script><template><div><input v-model="search" placeholder="搜索待办事项" /><div v-if="loading">加载中...</div><div v-else-if="error">{{ error.message }}</div><div v-else><ul><li v-for="todo in todos" :key="todo.id">{{ todo.title }}<button @click="handleDelete(todo.id)">删除</button></li></ul><button @click="prevPage" :disabled="page === 1">上一页</button><button @click="nextPage">下一页</button></div></div>
</template>
说明:
- 使用
request
函数调用getTodoList
,获取响应式状态。 search
和page
变化会自动触发请求(通过useWatcher
内部实现)。- 删除操作通过
request
调用deleteTodo
,并手动更新本地数据。
6. 使用封装的 API:用户登录表单 (src/components/UserForm.vue
)
<script setup lang="ts">
import { reactive } from 'vue';
import { request } from '../api';
import { login } from '../api/modules/userApi';const form = reactive({username: '',password: '',
});// 提交登录
const { loading, error, send } = request<{ token: string }>(login(form), { immediate: false });const handleSubmit = async () => {if (!form.username || !form.password) {alert('请填写完整信息');return;}const result = await send();if (!error.value) {localStorage.setItem('token', result.token);alert('登录成功');}
};
</script><template><div><input v-model="form.username" placeholder="用户名" /><input v-model="form.password" placeholder="密码" type="password" /><button :disabled="loading" @click="handleSubmit">{{ loading ? '登录中...' : '登录' }}</button><div v-if="error" style="color: red;">{{ error.message }}</div></div>
</template>
说明:
- 使用
request
调用login
API,手动触发请求。 - 登录成功后保存 token,供后续请求使用。
7. 主应用组件 (src/App.vue
)
<script setup lang="ts">
import TodoList from './components/TodoList.vue';
import UserForm from './components/UserForm.vue';
</script><template><div><h1>Alova 示例应用</h1><h2>用户登录</h2><UserForm /><h2>待办事项列表</h2><TodoList /></div>
</template>
8. 入口文件 (src/main.ts
)
import { createApp } from 'vue';
import App from './App.vue';const app = createApp(App);
app.mount('#app');
封装的优势与使用场景
优势
- 统一管理:所有 API 请求通过
alovaInstance
和request
函数管理,配置和拦截器集中定义。 - 简洁调用:组件只需调用
request
和 API 方法,无需关心底层实现。 - 类型安全:TypeScript 类型定义确保参数和返回数据的准确性。
- 模块化:API 按模块组织,便于维护和扩展。
- 响应式集成:与 Vue 3 的响应式系统无缝结合,状态变化自动更新视图。
使用场景
- 企业级应用:需要管理大量 API,封装后的结构清晰,适合团队协作。
- 动态交互:如分页、搜索、表单提交,
request
函数简化了状态管理。 - 高性能需求:通过 Alova 的缓存和请求共享,优化网络性能。
- 跨模块复用:API 文件可被多个组件复用,减少代码重复。
进阶优化
1. 添加请求防抖
在搜索场景中,添加防抖以减少高频请求:
import { useDebounce } from 'alova/client';export function requestWithDebounce<T>(method: Method, watchedStates: Ref<any>[], debounceDelay = 500) {return useDebounce(() => request<T>(method), watchedStates, { debounce: debounceDelay });
}
使用:
<script setup lang="ts">
import { ref } from 'vue';
import { requestWithDebounce } from '../api';
import { getTodoList } from '../api/modules/todoApi';const search = ref('');
const { loading, data: todos } = requestWithDebounce(getTodoList(1, 10, search.value), [search]);
</script>
2. 全局错误提示
在 alovaInstance
中添加全局错误提示:
import { Notify } from 'element-plus'; // 假设使用 Element PlusalovaInstance.responded.onError = (error) => {Notify({ type: 'error', message: error.message });throw error;
};
3. API 文档生成
使用 Alova 的 DevTools 或自定义脚本,从 Method
实例生成 API 文档,提升开发效率。
总结
通过对 Alova 的封装,我们创建了一个统一的 request
函数和模块化的 API 管理方案,极大简化了 Vue 3 项目中的请求逻辑。封装后的代码结构清晰、类型安全、易于维护,适合从小型项目到企业级应用的各种场景。Alova 的响应式状态管理、缓存机制和内置策略进一步提升了开发效率和用户体验。
快速开始
- 安装 Alova:
npm install alova alova/vue alova/fetch
- 复制上述代码结构,调整
baseURL
和 API 定义。 - 参考官方文档 alova.js.org 和 GitHub 深入学习。