做前端的同学常遇到这种场景:点击登录按钮,页面卡住几秒没反应,用户反复点,结果发了三遍请求——最后发现账号被锁了。问题不在后端,而在前端没管好异步流程。
MVVM里,异步不是“等完再更新”,而是“边跑边说话”
以 Vue 为例,数据绑定靠的是响应式系统。你不能让 user 等着接口返回才赋值,而要让界面提前知道:“正在请求中”“成功了”“失败了”。关键就三个状态:loading、data、error。
一个真实的登录组件写法
假设用 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",物理禁用; - 请求前先清空上一次的
error和data,防止旧数据残留。
另外,如果多个异步操作共用一个 loading 状态(比如搜索+分页),建议拆成独立变量,比如 searchLoading 和 pageLoading,不然容易互相干扰。