背景

搜索页连续输入时,旧请求可能比新请求更晚返回。单独维护 loading、error、data,既容易组合出矛盾状态,也掩盖了响应乱序。

核心判断

  1. 用 idle、loading、success、error 表达互斥状态,空列表属于 success。
  2. 每次请求携带递增序号或取消信号,只有当前请求可以提交结果。
  3. 刷新旧数据时另行定义 refreshing 状态,明确是否保留旧内容。

最小示例

下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。

type State<T> =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "success"; data: T }
  | { kind: "error"; message: string };

const state = shallowRef<State<User[]>>({ kind: "idle" });
let sequence = 0;

async function search(query: string) {
  const current = ++sequence;
  state.value = { kind: "loading" };
  try {
    const data = await api.search(query);
    if (current === sequence) state.value = { kind: "success", data };
  } catch (error) {
    if (current === sequence) {
      state.value = { kind: "error", message: String(error) };
    }
  }
}

边界与误区

状态类型只能约束组合,不能自动解决竞态。仅在 finally 里设置 loading=false,也可能让旧请求提前结束新请求的加载提示。

验证方式

用可控 Promise 让第二次请求先完成,确认第一次响应被丢弃;再覆盖初始、空结果、失败、取消和重试的界面表现。

小结

用有限状态表达 idle、loading、success、error,避免多个布尔值形成不可能组合。