UniApp Vue3组合式API实战:封装带重试与缓存机制的全局网络请求模块

2026-06-26 0 844

UniApp开发中,网络请求是不可或缺的一环。每次在页面里写一遍 uni.request ,再单独处理 loading、错误提示、令牌刷新,不仅代码重复,还容易遗漏某些边界情况。尤其是弱网环境下,请求失败后需要自动重试,或者某些公共数据需要缓存以降低服务器压力——这些逻辑如果分散在各个页面,维护起来十分费力。

Vue3 的组合式 API 提供了天然的逻辑聚合能力。本篇文章就动手封装一个功能完备的请求模块,把重试、缓存、令牌注入、统一错误处理都关进一个可复用的组合函数里。同时集成 Pinia 来管理令牌状态,让整个请求层与业务状态自然联动。所有代码可直接在 UniApp Vue3 项目中运行,兼容小程序、H5 和 App 端。

一、项目基础与目录规划

使用 HBuilderX 或 CLI 创建一个默认的 UniApp Vue3 模板。安装 Pinia 用于全局状态管理:

npm install pinia

main.js 中注册 Pinia:

import { createSSRApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';

export function createApp() {
    const app = createSSRApp(App);
    app.use(createPinia());
    return { app };
}

项目目录结构重点如下:

src/
├── api/                    # 接口层
│   └── user.js             # 用户相关接口示例
├── composables/            # 组合函数
│   └── useRequest.js       # 核心请求封装
├── stores/                 # Pinia 状态
│   └── user.js             # 用户令牌管理
├── utils/
│   ├── http.js             # uni.request 基础封装
│   └── storage.js          # 跨平台缓存适配
└── pages/

二、跨平台存储适配层

小程序端没有 localStorage ,需要使用 uni.setStorageSyncuni.getStorageSync 作为统一接口。为了避免在请求模块里直接耦合 UniApp API,我们先写一个轻量的存储工具。

// utils/storage.js
const storage = {
    get(key) {
        try {
            return uni.getStorageSync(key);
        } catch (e) {
            return null;
        }
    },
    set(key, value) {
        try {
            uni.setStorageSync(key, value);
        } catch (e) {
            console.error('存储失败', e);
        }
    },
    remove(key) {
        try {
            uni.removeStorageSync(key);
        } catch (e) {}
    }
};
export default storage;

这个简单的工具层保证了后续缓存功能在各端可用。

三、基础请求封装与拦截器

基于 uni.request 封装一个返回 Promise 的基础请求函数,同时抛出统一的错误格式,避免每次调用都要判断 res.statusCode

// utils/http.js
import storage from './storage.js';

// 基础请求,自动携带 token
function request(config) {
    const token = storage.get('access_token') || '';
    const header = {
        'Content-Type': 'application/json',
        ...config.header
    };
    if (token) {
        header['Authorization'] = `Bearer ${token}`;
    }

    return new Promise((resolve, reject) => {
        uni.request({
            ...config,
            header,
            success(res) {
                if (res.statusCode >= 200 && res.statusCode < 300) {
                    resolve(res.data);
                } else if (res.statusCode === 401) {
                    // 令牌过期,可在此触发刷新或跳转登录
                    storage.remove('access_token');
                    reject({ code: 401, message: '登录已过期' });
                } else {
                    reject({
                        code: res.statusCode,
                        message: res.data?.message || '请求失败'
                    });
                }
            },
            fail(err) {
                reject({
                    code: -1,
                    message: err.errMsg || '网络异常'
                });
            }
        });
    });
}

export default request;

这里已经实现了最简单的拦截器:401 时清除令牌并拒绝。后续可以在 Pinia 中监听状态变化,统一跳转登录页。

四、组合式请求模块:重试与缓存核心

接下来是重头戏——用组合式 API 创建一个 useRequest 函数。它接收请求配置,返回响应式数据、loading 状态以及手动触发的方法。内部集成重试计数器和缓存命中的逻辑。

// composables/useRequest.js
import { ref, unref } from 'vue';
import request from '@/utils/http.js';
import storage from '@/utils/storage.js';

/**
 * 请求组合函数
 * @param {object|function} configOrFn 静态配置或返回配置的函数
 * @param {object} options 额外选项
 * @param {number} options.retryCount 最大重试次数,默认2
 * @param {number} options.cacheTime 缓存有效期(ms),0表示不缓存
 * @param {string} options.cacheKey 缓存键,若不传则根据url+参数自动生成
 */
export function useRequest(configOrFn, options = {}) {
    const {
        retryCount = 2,
        cacheTime = 0,
        cacheKey = ''
    } = options;

    const data = ref(null);
    const error = ref(null);
    const loading = ref(false);
    let retryAttempt = 0;

    // 根据参数生成缓存 key
    function resolveCacheKey(config) {
        if (cacheKey) return cacheKey;
        const { url, data } = config;
        return `req_cache_${url}_${JSON.stringify(data || {})}`;
    }

    // 检查缓存是否有效
    function getCache(config) {
        if (cacheTime  cacheTime) {
            storage.remove(key);
            return null;
        }
        return cached.data;
    }

    // 存储缓存
    function setCache(config, result) {
        if (cacheTime <= 0) return;
        const key = resolveCacheKey(config);
        storage.set(key, {
            data: result,
            timestamp: Date.now()
        });
    }

    async function run(overrideConfig = {}) {
        const config = typeof configOrFn === 'function'
            ? { ...configOrFn(), ...overrideConfig }
            : { ...configOrFn, ...overrideConfig };

        // 先尝试读取缓存
        const cached = getCache(config);
        if (cached) {
            data.value = cached;
            return cached;
        }

        loading.value = true;
        error.value = null;

        try {
            const result = await request(config);
            data.value = result;
            setCache(config, result);
            retryAttempt = 0;
            return result;
        } catch (err) {
            // 自动重试
            if (retryAttempt < retryCount) {
                retryAttempt++;
                console.warn(`请求失败,正在进行第${retryAttempt}次重试...`, err);
                return run(overrideConfig); // 递归重试
            }
            error.value = err;
            throw err;
        } finally {
            loading.value = false;
        }
    }

    // 清除当前缓存
    function clearCache() {
        const config = typeof configOrFn === 'function' ? configOrFn() : configOrFn;
        const key = resolveCacheKey(config);
        storage.remove(key);
    }

    return {
        data,
        error,
        loading,
        run,
        clearCache
    };
}

这个模块的核心思路:请求前先查缓存,如果命中且在有效期内就直接返回,否则发起真实请求。请求失败时自动重试,直到达到 retryCount 上限。缓存键默认根据 URL 和参数自动生成,避免重复请求。同时暴露 clearCache 方法,方便上层在需要时强制刷新。

五、Pinia 令牌管理与业务接口

我们在 Pinia 中维护用户登录后的令牌,并提供登录和退出方法。这样在请求拦截器里就可以实时获取最新令牌。

// stores/user.js
import { defineStore } from 'pinia';
import request from '@/utils/http.js';
import storage from '@/utils/storage.js';

export const useUserStore = defineStore('user', {
    state: () => ({
        token: storage.get('access_token') || '',
        userInfo: null
    }),
    actions: {
        async login(username, password) {
            const res = await request({
                url: '/api/login',
                method: 'POST',
                data: { username, password }
            });
            this.token = res.token;
            storage.set('access_token', res.token);
            return res;
        },
        logout() {
            this.token = '';
            this.userInfo = null;
            storage.remove('access_token');
        }
    }
});

接着基于 useRequest 封装实际的业务接口。比如一个获取用户详情并缓存 60 秒的接口:

// api/user.js
import { useRequest } from '@/composables/useRequest.js';

export function useUserDetail(userId) {
    const { data, loading, error, run, clearCache } = useRequest(
        () => ({
            url: `/api/user/${userId}`,
            method: 'GET'
        }),
        {
            retryCount: 1,
            cacheTime: 60 * 1000,   // 缓存 60 秒
            cacheKey: `user_detail_${userId}`
        }
    );
    return { user: data, loading, error, fetch: run, refresh: clearCache };
}

这里用函数形式返回配置,确保每次使用不同的 userId 时都能生成正确的缓存键。

六、页面中的调用示例

在一个用户详情页中,我们使用上面封装好的接口:

<template>
    <view class="container">
        <view v-if="loading">加载中...</view>
        <view v-else-if="error">
            <text>加载失败: {{ error.message }}</text>
            <button @tap="handleRetry">重试</button>
        </view>
        <view v-else>
            <text>昵称: {{ user?.nickname }}</text>
            <text>邮箱: {{ user?.email }}</text>
            <button @tap="refresh">强制刷新(清除缓存)</button>
        </view>
    </view>
</template>

<script setup>
import { onMounted } from 'vue';
import { useUserDetail } from '@/api/user.js';

const userId = '123';
const { user, loading, error, fetch, refresh } = useUserDetail(userId);

onMounted(() => {
    fetch();
});

function handleRetry() {
    fetch();
}
</script>

这里展示了一个典型的加载、错误、数据三态切换,以及手动重试和清除缓存的功能。在弱网环境下,用户点击“重试”按钮会再次发起请求;点击“强制刷新”会先清空缓存,然后重新拉取最新数据。整个交互逻辑简洁明了,且页面不包含任何 uni.request 的底层细节。

七、小程序端特殊适配

小程序环境有一些独有的限制需要处理:

  • 请求超时与并发:小程序 uni.request 的并发限制为 10 个,本封装模块可正常使用,但在极端场景下建议通过队列控制数量。
  • Storage 容量:单个 key 数据上限为 1MB,总计 10MB。缓存数据应注意大小,避免存储二进制内容。
  • 自动重试:弱网下重试次数不宜过多,避免消耗过多请求配额,默认 2 次已经足够。
  • 令牌刷新:401 处理逻辑中,如果需要自动刷新令牌,可在 http.js 的 401 分支中调用 Pinia 的 refreshToken 方法,但要防止并发请求同时触发多次刷新(可以加一个刷新锁)。

八、总结

通过 Vue3 组合式 API 和 Pinia 的配合,我们构建出了一个高度内聚且可复用的网络请求层。它对外只暴露 dataloadingerror 等响应式状态,内部自动处理令牌注入、错误重试和缓存命中,大幅减少了业务代码中的重复逻辑。再加上跨平台的存储适配,这个模块在 UniApp 的各种端上都能平稳运行。

下一次开发 UniApp 项目时,可以直接把这个 useRequest 组合函数和 http.js 复制到新工程里,在接口文件中按模板编写业务请求,你会发现曾经散落在各处的 loading 和错误处理突然变得井井有条。

UniApp Vue3组合式API实战:封装带重试与缓存机制的全局网络请求模块
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp UniApp Vue3组合式API实战:封装带重试与缓存机制的全局网络请求模块 https://www.taomawang.com/web/uniapp/2285.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务