🌐 Taro+Vue3 入门(七):网络请求封装
系列导读:小程序请求和 Web 的 fetch/axios 不同,需要用
Taro.request。 本文教你封装统一请求层、自动鉴权和 API 模块化。
🔧 1. 统一封装
// src/utils/request.ts
import Taro from '@tarojs/taro'
import { useAuthStore } from '@/stores/auth'
const BASE_URL = 'https://api.example.com'
interface RequestConfig {
url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: any
loading?: boolean
toast?: boolean
}
interface ApiResponse<T = any> {
code: number
message: string
data: T
}
export async function request<T = any>(config: RequestConfig): Promise<T> {
const { url, method = 'GET', data, loading = false, toast = true } = config
if (loading) Taro.showLoading({ title: '加载中...', mask: true })
// 自动注入 Token
const header: Record<string, string> = { 'Content-Type': 'application/json' }
const authStore = useAuthStore()
if (authStore.token) {
header['Authorization'] = `Bearer ${authStore.token}`
}
try {
const res = await Taro.request<ApiResponse<T>>({
url: `${BASE_URL}${url}`,
method,
data,
header,
timeout: 10000,
})
if (loading) Taro.hideLoading()
if (res.statusCode !== 200) throw new Error(`HTTP ${res.statusCode}`)
const { code, message, data: resData } = res.data
if (code === 401) {
authStore.logout()
Taro.reLaunch({ url: '/pages/login/index' })
throw new Error('登录已过期')
}
if (code !== 0) throw new Error(message || '请求失败')
return resData
} catch (error: any) {
if (loading) Taro.hideLoading()
if (toast) Taro.showToast({ title: error?.message || '网络异常', icon: 'none' })
throw error
}
}
export const http = {
get: <T>(url: string, data?: any, config?: Partial<RequestConfig>) =>
request<T>({ url, method: 'GET', data, ...config }),
post: <T>(url: string, data?: any, config?: Partial<RequestConfig>) =>
request<T>({ url, method: 'POST', data, ...config }),
put: <T>(url: string, data?: any, config?: Partial<RequestConfig>) =>
request<T>({ url, method: 'PUT', data, ...config }),
delete: <T>(url: string, data?: any, config?: Partial<RequestConfig>) =>
request<T>({ url, method: 'DELETE', data, ...config }),
}
📡 2. API 模块化
// src/api/product.ts
import { http } from '@/utils/request'
export interface Product {
id: string
name: string
price: number
image: string
description: string
}
export const productApi = {
getList: (params: { page: number; pageSize: number; category?: string }) =>
http.get<{ list: Product[]; total: number }>('/api/products', params),
getDetail: (id: string) =>
http.get<Product>(`/api/products/${id}`, undefined, { loading: true }),
search: (keyword: string) =>
http.get<Product[]>('/api/products/search', { keyword }),
}
🪝 3. 请求 Composable
// src/composables/useRequest.ts
import { ref, type Ref } from 'vue'
export function useRequest<T>(fetcher: () => Promise<T>) {
const data: Ref<T | null> = ref(null)
const loading = ref(false)
const error = ref<Error | null>(null)
async function run() {
loading.value = true
error.value = null
try {
data.value = await fetcher() as any
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
run()
return { data, loading, error, refresh: run }
}
<!-- 使用 -->
<script setup lang="ts">
import { useRequest } from '@/composables/useRequest'
import { productApi } from '@/api/product'
const { data, loading, refresh } = useRequest(() =>
productApi.getList({ page: 1, pageSize: 20 })
)
usePullDownRefresh(async () => {
await refresh()
Taro.stopPullDownRefresh()
})
</script>
<template>
<nut-skeleton :loading="loading" :row="5" animated>
<ProductCard v-for="p in data?.list" :key="p.id" v-bind="p" />
</nut-skeleton>
</template>
✅ 本篇小结 Checklist
- 封装统一请求层(Token/错误处理/Loading)
- 按模块组织 API
- 会写请求 Composable
本文是「Taro+Vue3 入门」系列第 7 篇,共 10 篇。
📝 作者:NIHoa | 系列:Taro+Vue3入门系列 | 更新日期:2024-05-07