小程序上传进度条,还要自己监听 onProgressUpdate 吗?

你小程序里多半有一段长得像这样的上传代码:

javascript 复制代码
// pages/feedback/index.vue
export default {
  data() {
    return { uploading: false, percent: 0, uploadTask: null };
  },
  methods: {
    handleUpload() {
      uni.chooseImage({
        count: 1,
        success: res => {
          this.uploading = true;
          const task = uni.uploadFile({
            url: 'https://api.example.com/uploadImg',
            filePath: res.tempFilePaths[0],
            name: 'file',
            formData: { from: 'feedback' },
            success: () => { this.uploading = false; },
            fail: () => {
              this.uploading = false;
              uni.showToast({ title: '上传失败', icon: 'none' });
            }
          });
          task.onProgressUpdate(e => { this.percent = e.progress; });
          this.uploadTask = task; // 存起来,取消时才用得到
        }
      });
    },
    handleCancel() {
      this.uploadTask && this.uploadTask.abort();
      this.uploading = false;
      this.percent = 0;
    }
  }
};

它能跑,也上线了。但这段代码有三处不对劲:

  1. 状态是手工账。 uploadingpercentuploadTask 三个字段,得在 success / fail / 取消三条路径里挨个复位。哪天加个"失败自动清进度",漏改一条路径,进度条就永远停在 73%。
  2. 取消靠自觉。 uploadTask 引用得自己存,取消得自己调 abort(),复位得自己写。页面一销毁忘了取消,请求还在后台跑。
  3. 没法复用。 头像、意见反馈、工单附件,每个页面把五十行复制一遍改个 url。三个月后想统一加登录态过期处理,得找出所有副本挨个改。

用户那头的体感更直接:点了上传没进度,以为卡死,再点一次,服务器收到两张一样的图。

理想的上传该长什么样

把需求摊开,其实就四条:

  • 进度、加载态是响应式状态,不是手工变量,任何路径退出都自动复位;
  • 取消是内置动作,不用自己攒 task 引用;
  • 重传就是再调一次,不用把 chooseImage 到 uploadFile 整条链重走;
  • 写法和普通请求一致,上传不该是一套独立于 GET/POST 之外的方言。

第 4 条最容易被忽略,但它决定三个月后的维护成本:分页一套写法、上传又一套写法,团队每个人都在维护好几套私房封装。

顺着这份清单找,会发现这正是请求策略库在做的事。alova 的定位是一套请求 API 跑在 Web、小程序/App(uni-app/Taro)、服务端,上传只是其中一个策略场景。

换成 alova 写一遍

先装包(uni-app 适配器,注意只支持 vue3 版 uni-app):

bash 复制代码
npm install alova @alova/adapter-uniapp @alova/shared --save

建实例,请求/存储适配器一次配齐:

javascript 复制代码
// api/index.js
import { createAlova } from 'alova';
import AdapterUniapp from '@alova/adapter-uniapp';

export const alovaInst = createAlova({
  baseURL: 'https://api.example.com',
  ...AdapterUniapp(),
  responded(response) {
    const { statusCode, data } = response;
    if (statusCode >= 400) throw new Error('请求错误');
    return data || null;
  }
});

上传本身,在 method 的 config 里标 requestType: 'upload',适配器内部就改调 uni.uploadFiledata 里的 namefilePath 传给 uploadFile,其余字段自动进 formData

html 复制代码
<template>
  <button @click="handleImageChoose">上传图片</button>
  <button v-if="loading" @click="abort">取消</button>
  <progress v-if="loading" :percent="percent" show-info />
</template>

<script setup>
import { computed } from 'vue';
import { useRequest } from 'alova/client';
import { alovaInst } from '@/api';

const uploadFile = (name, filePath, formData) =>
  alovaInst.Post(
    '/uploadImg',
    { name, filePath, ...formData }, // 额外字段自动进 formData
    { requestType: 'upload', fileType: 'image' }
  );

const { loading, uploading, send, abort } = useRequest(uploadFile, {
  immediate: false
});

// uploading = { loaded, total },响应式,直接算百分比
const percent = computed(() =>
  uploading.value.total > 0
    ? Math.floor((uploading.value.loaded / uploading.value.total) * 100)
    : 0
);

const handleImageChoose = () => {
  uni.chooseImage({
    count: 1,
    success: res => send('file', res.tempFilePaths[0], { from: 'feedback' })
  });
};
</script>

对照开头那三条清单:

  • 进度:uploading.loaded / uploading.total,响应式,没有任何一行 onProgressUpdate
  • 加载态:loading 自动起落,失败、取消都会复位,不存在"进度条卡在 73%";
  • 取消:解构出来的 abort() 直接绑按钮,task 引用的存取是库内部的事;
  • 重传:失败后再点按钮,send() 再走一次就是重传,参数还能换。

dataloading 这些和普通请求完全同名同形,这就是清单第 4 条:上传不再是方言。团队里会写 useRequest(getList) 的人,看这段代码零学习成本。

顺带一提,如果你项目已经在用 axios,它不冲突:axios 可以作为 alova 的请求适配器继续发请求(@alova/adapter-axios),axios 负责发请求,alova 负责怎么请求。不过小程序环境用不到 axios,这里按下不表。

多文件、批量怎么办

单文件够用就到此为止。要做"九宫格传图 + 每张独立进度 + 失败单张重传",alova@3.3+ 还有个 useUploader:base64 / File / Blob / ArrayBuffer 直接丢进去,每个文件独立的 status 和 progress,upload(1, 3) 指定重传,mode: 'batch' 批量提交。要说明的是:它默认的弹窗选文件只在浏览器里有效,小程序/uni-app 环境需要通过 useUploader.selectFile 自定义选择逻辑。单文件场景用上面的 useRequest 写法就好,不必上它。


alova 是一套请求 API,跑在 Web、小程序/App(uni-app/Taro)和服务端(BFF)。上传只是入口,分页竞态、token 并发刷新、请求缓存这些"每个项目都要再写一遍"的东西,才是请求策略层真正省心的地方。

相关推荐
古韵1 小时前
axios 已经跑得好好的,还要不要上 alova?
前端·javascript·axios
噗噗121 小时前
企业微信侧边栏应用与前端 JS-SDK 的深度整合指南
前端·javascript·企业微信
朝阳391 小时前
react19【实战】配置化路由登录鉴权完整方案
前端·javascript·react.js
ShuiShenHuoLe1 小时前
vue如何实现国际化,它来了!
前端·javascript·vue.js
其美杰布-富贵-李1 小时前
Vue Router 实战教程
前端·javascript·vue.js
NutShell Wang1 小时前
拆解 GitHub gh-stack:堆叠 PR 工作流的设计取舍与工程实现
前端·git·开源·github·代码复审·开发者工具·vibe coding
名字还没想好☜2 小时前
React 用 IntersectionObserver 实现图片懒加载与无限滚动:封装一个 useInView Hook
前端·javascript·vue.js·react.js·react
带娃的IT创业者2 小时前
Puppeteer 深度解析:超越自动化测试的现代 Web 交互范式
前端·交互·puppeteer·可观测性·浏览器自动化·无头浏览器·web工程化
程序员爱钓鱼2 小时前
Rust 所有权 Ownership 详解:理解内存安全的核心机制
前端·后端·rust