如何在未知提前传参时合理使用useAxios?优化TS服务实现
优化基于Vue组合式函数的TypeScript服务实现
现有实现回顾
你目前基于Vue组合式函数和DummyJSON后端构建了Todo服务,核心步骤如下:
1. 配置Axios基础URL
// src/axios/index.ts import axios from 'axios' axios.defaults.baseURL = 'https://dummyjson.com'
2. 全局导入Axios配置
// main.ts import { createApp } from 'vue' import App from './App.vue' import './axios' const app = createApp(App) app.mount('#app')
3. 初始Todo服务实现
// src/composables/services/todo.service.ts import { useAxios } from '@vueuse/integrations/useAxios' const BASE_URL = '/todos' const useTodoService = { getAll: () => { return useAxios(`${BASE_URL}`) // 默认GET请求,立即执行 } }
4. 组件中调用服务(立即加载)
<!-- src/views/TestView.vue --> <script setup lang="ts"> import useTodoService from '@/composables/services/todo.service' const { data, isLoading } = useTodoService.getAll() </script> <template> <div>Loading: {{ isLoading }}</div> <div>First todo: {{ data?.todos[0] }}</div> </template>
5. 改为点击触发请求
更新服务:
// src/composables/services/todo.service.ts const useTodoService = { getAll: () => { return useAxios( `${BASE_URL}`, {}, { immediate: false } ) } }
组件中调用:
<!-- src/views/TestView.vue --> <script setup lang="ts"> import useTodoService from '@/composables/services/todo.service' const { data, isLoading, execute } = useTodoService.getAll() </script> <template> <div>Loading: {{ isLoading }}</div> <div>First todo: {{ data?.todos[0] }}</div> <button @click="execute">Fire!</button> </template>
6. 带参数的POST请求实现
更新服务(自定义execute):
// src/composables/services/todo.service.ts const useTodoService = { getAll: () => { // 与之前代码一致 }, add: () => { const useAxiosObj = useAxios( `${BASE_URL}/add`, { method: 'POST' }, { immediate: false } ) const execute = (title: string) => { return useAxiosObj.execute({ data: { todo: title, completed: false, userId: 5 } }) } return { ...useAxiosObj, execute } } }
组件中调用:
<!-- src/views/TestView.vue --> <script setup lang="ts"> import useTodoService from '@/composables/services/todo.service' const { data, isLoading, execute } = useTodoService.add() function onClick() { execute('Lorem ipsum...') } </script> <template> <div>Loading: {{ isLoading }}</div> <div>Added todo: {{ data }}</div> <button @click="onClick">Fire!</button> </template>
现有实现的问题
- 自定义
execute函数不符合useAxios的标准API,熟悉该工具的开发者会产生认知偏差 - 扩展
useAxios返回对象时触发IDE警告:Expected a non-Promise value to be spreaded in an object - 重复代码多,抽象层构建繁琐,难以统一处理请求/响应逻辑
优化方案
1. 封装通用请求组合式函数
创建useApi统一处理请求配置、响应转换和基础逻辑,避免重复代码:
// src/composables/useApi.ts import { computed } from 'vue' import { useAxios } from '@vueuse/integrations/useAxios' import type { AxiosRequestConfig, UseAxiosOptions } from '@vueuse/integrations/useAxios' export function useApi<T = any>( url: string, config?: AxiosRequestConfig, options?: Omit<UseAxiosOptions<T>, 'immediate'> ) { const { data, isLoading, error, execute, ...rest } = useAxios<T>( url, config, { immediate: false, ...options } ) // 统一提取响应数据(适配DummyJSON的返回结构) const transformedData = computed(() => data.value?.data ?? null) // 自定义请求触发函数,支持传入业务参数 const request = async (params?: { params?: any; data?: any }) => { const requestConfig: AxiosRequestConfig = { ...config, ...(params?.params && { params: params.params }), ...(params?.data && { data: params.data }) } return execute(requestConfig) } return { data: transformedData, isLoading, error, request, execute, // 保留原execute方法,兼容原有使用习惯 ...rest } }
2. 重构Todo服务为规范的组合式函数
改用函数返回业务方法的结构,避免对象包裹,同时封装业务逻辑:
// src/composables/services/useTodoService.ts import { useApi } from '../useApi' const BASE_URL = '/todos' export function useTodoService() { // 获取所有Todo(立即执行) const getAll = () => { return useApi(`${BASE_URL}`, { method: 'GET' }, { immediate: true }) } // 添加Todo(手动触发) const useAddTodo = () => { const { data, isLoading, request } = useApi(`${BASE_URL}/add`, { method: 'POST' }) // 业务化的添加方法,对外暴露简洁参数 const addTodo = async (title: string) => { // 这里统一处理业务参数转换 return request({ data: { todo: title, completed: false, userId: 5 } }) } return { data, isLoading, addTodo, request // 保留底层请求方法,留扩展空间 } } return { getAll, useAddTodo } }
3. 组件中使用优化后的服务
<!-- src/views/TestView.vue --> <script setup lang="ts"> import { useTodoService } from '@/composables/services/useTodoService' const { getAll, useAddTodo } = useTodoService() // 自动加载所有Todo const { data: todosData, isLoading: todosLoading } = getAll() // 处理添加Todo逻辑 const { data: addedTodo, isLoading: addLoading, addTodo } = useAddTodo() const handleAddTodo = async () => { await addTodo('Lorem ipsum...') } </script> <template> <div>加载Todos: {{ todosLoading }}</div> <div>第一条Todo: {{ todosData?.todos[0] }}</div> <div>添加Todo状态: {{ addLoading }}</div> <div>已添加Todo: {{ addedTodo }}</div> <button @click="handleAddTodo">添加Todo</button> </template>
优化后的优势
- 完全遵循Vue组合式函数规范,结构清晰易维护
- 通用请求逻辑集中在
useApi,避免重复代码,便于统一修改 - 业务服务对外暴露简洁的业务方法,同时保留底层API的扩展性
- 解决IDE警告:不再直接扩展包含Promise的
useAxios返回对象,通过组合方式构建返回值 - 统一处理响应数据转换,无需在每个服务中重复实现
内容的提问来源于stack exchange,提问作者PyKKe
相关产品推荐
相关产品推荐

