MVVM如何处理异步请求?手把手写个带加载状态的登录页

做前端的同学常遇到这种场景:点击登录按钮,页面卡住几秒没反应,用户反复点,结果发了三遍请求——最后发现账号被锁了。问题不在后端,而在前端没管好异步流程。

MVVM里,异步不是“等完再更新”,而是“边跑边说话”

以 Vue 为例,数据绑定靠的是响应式系统。你不能让 user 等着接口返回才赋值,而要让界面提前知道:“正在请求中”“成功了”“失败了”。关键就三个状态:loadingdataerror

一个真实的登录组件写法

假设用 Vue 3 + Composition API:

import { ref } from "vue";

export default {
  setup() {
    const loading = ref(false);
    const userInfo = ref(null);
    const errorMsg = ref("");

    const login = async (form) => {
      loading.value = true;
      errorMsg.value = "";
      
      try {
        const res = await fetch("/api/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(form)
        });
        
        if (!res.ok) throw new Error("网络错误或账号异常");
        
        userInfo.value = await res.json();
      } catch (err) {
        errorMsg.value = err.message;
      } finally {
        loading.value = false;
      }
    };

    return { loading, userInfo, errorMsg, login };
  }
};

模板里就这么用:

<button @click="login({ username, password })" :disabled="loading">
  {{ loading ? "登录中..." : "立即登录" }}
</button>
<p v-if="errorMsg" class="error">{{ errorMsg }}</p>
<div v-if="userInfo" class="welcome">欢迎,{{ userInfo.name }}!</div>

别只顾着“拿到数据”,得告诉 UI “现在在哪儿”

很多人写 MVVM 异步,只写 await api(),然后直接赋值。但用户看不到过程,体验就断了。MVVM 的优势恰恰是把“状态”变成可绑定的数据:loading 是个布尔值,error 是个字符串,它们和视图天然联动。

比如微信小程序里用 WXML 做 MVVM 风格开发,也是类似逻辑:data: { loading: false, list: [] },请求开始设 loading=true,结束再改回来——哪怕只是加个旋转图标,用户心里就有底。

小技巧:避免重复提交和状态错乱

实际项目中,常加两道保险:

  • 按钮加 :disabled="loading",物理禁用;
  • 请求前先清空上一次的 errordata,防止旧数据残留。

另外,如果多个异步操作共用一个 loading 状态(比如搜索+分页),建议拆成独立变量,比如 searchLoadingpageLoading,不然容易互相干扰。