🌐 Taro 从零到一(七):网络请求与数据处理
系列导读:小程序连接后端从
Taro.request开始。 本文教你封装统一请求层,处理 Token、刷新、错误、loading。
🛠 1. Taro.request 基础
import Taro from '@tarojs/taro'
// 基础请求
const res = await Taro.request({
url: 'https://api.example.com/products',
method: 'GET',
header: { 'Content-Type': 'application/json' },
})
console.log(res.data)
🔧 2. 统一封装
// src/utils/request.ts
import Taro from '@tarojs/taro'
import useAuthStore from '@/store/useAuthStore'
const BASE_URL = 'https://api.example.com'
interface RequestOptions {
url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: any
header?: Record<string, string>
loading?: boolean // 是否显示 loading
toast?: boolean // 是否显示错误提示
}
interface ApiResponse<T = any> {
code: number
message: string
data: T
}
async function request<T = any>(options: RequestOptions): Promise<T> {
const { url, method = 'GET', data, header = {}, loading = false, toast = true } = options
// 自动注入 Token
const token = useAuthStore.getState().token
if (token) {
header['Authorization'] = `Bearer ${token}`
}
// 显示 loading
if (loading) {
Taro.showLoading({ title: '加载中...', mask: true })
}
try {
const res = await Taro.request<ApiResponse<T>>({
url: `${BASE_URL}${url}`,
method,
data,
header: { 'Content-Type': 'application/json', ...header },
timeout: 10000,
})
// 隐藏 loading
if (loading) Taro.hideLoading()
// HTTP 状态码检查
if (res.statusCode !== 200) {
throw new Error(`HTTP ${res.statusCode}`)
}
// 业务码检查
const { code, message, data: resData } = res.data
if (code === 401) {
// Token 过期,跳转登录
useAuthStore.getState().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()
const msg = error?.message || '网络异常,请稍后重试'
if (toast) {
Taro.showToast({ title: msg, icon: 'none', duration: 2000 })
}
throw error
}
}
// 快捷方法
export const http = {
get: <T>(url: string, data?: any, options?: Partial<RequestOptions>) =>
request<T>({ url, method: 'GET', data, ...options }),
post: <T>(url: string, data?: any, options?: Partial<RequestOptions>) =>
request<T>({ url, method: 'POST', data, ...options }),
put: <T>(url: string, data?: any, options?: Partial<RequestOptions>) =>
request<T>({ url, method: 'PUT', data, ...options }),
delete: <T>(url: string, data?: any, options?: Partial<RequestOptions>) =>
request<T>({ url, method: 'DELETE', data, ...options }),
}
export default request
📡 3. API 模块化
// src/api/product.ts
import { http } from '@/utils/request'
export interface Product {
id: string
name: string
price: number
image: string
description: string
sales: number
tags: string[]
}
interface ProductListParams {
page: number
pageSize: number
category?: string
keyword?: string
}
interface PaginatedList<T> {
list: T[]
total: number
page: number
}
export const productApi = {
// 商品列表
getList: (params: ProductListParams) =>
http.get<PaginatedList<Product>>('/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 }),
}
// src/api/auth.ts
import { http } from '@/utils/request'
export const authApi = {
// 微信登录
loginByWechat: (code: string) =>
http.post<{ token: string; user: User }>('/api/auth/wechat', { code }),
// 手机号登录
loginByPhone: (phone: string, smsCode: string) =>
http.post<{ token: string; user: User }>('/api/auth/phone', { phone, smsCode }),
// 获取用户信息
getUserInfo: () =>
http.get<User>('/api/user/info'),
}
🪝 4. 自定义请求 Hook
// src/hooks/useRequest.ts
import { useState, useEffect, useCallback } from 'react'
interface UseRequestResult<T> {
data: T | null
loading: boolean
error: Error | null
refresh: () => Promise<void>
}
export function useRequest<T>(
fetcher: () => Promise<T>,
options: { immediate?: boolean } = { immediate: true }
): UseRequestResult<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const run = useCallback(async () => {
setLoading(true)
setError(null)
try {
const result = await fetcher()
setData(result)
} catch (e) {
setError(e as Error)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
if (options.immediate) run()
}, [])
return { data, loading, error, refresh: run }
}
// 使用
function ProductListPage() {
const { data, loading, refresh } = useRequest(() =>
productApi.getList({ page: 1, pageSize: 20 })
)
usePullDownRefresh(async () => {
await refresh()
Taro.stopPullDownRefresh()
})
if (loading) return <Skeleton rows={5} />
return (
<View>
{data?.list.map(p => <ProductCard key={p.id} product={p} />)}
</View>
)
}
✅ 本篇小结 Checklist
- 掌握
Taro.request基础用法 - 封装统一请求层(Token 注入、错误处理、loading)
- 按模块组织 API(productApi / authApi)
- 会写自定义请求 Hook
下一篇预告:《多端适配与条件编译》
本文是「Taro 从零到一」系列第 7 篇,共 10 篇。