FORMA

选项卡切换与 Loading 状态封装

在业务页面中,选项卡切换是高频交互场景。例如切换商品分类时,通常需要携带对应 id 重新请求数据。

如果不做统一封装,常见问题包括:

  • 每个页面重复编写请求逻辑;
  • loading/success/error 三态实现不一致;
  • 重试逻辑分散,维护成本高。

因此,更推荐将该能力抽象为统一的 useFetch 方案。

方案目标

统一处理以下状态:

  • loading:请求进行中;
  • success:请求成功并渲染结果;
  • error:请求失败并支持手动重试。

解析示意图: 解析图


实现思路(Vue)

核心点是监听“请求依赖”的变化(如 idurl),并自动触发请求:

  • computed 维护派生请求地址;
  • watchEffect 自动追踪依赖并执行请求;
  • unref 兼容 ref 与普通值参数。

依赖监听示例

javascript
import { computed, ref, watchEffect } from "vue";

const id = ref(1);
const changeId = () => id.value++;

const url = computed(() => `https://api-xxxxx.com?id=${id.value}`);

watchEffect(() => {
  console.log("watchEffect url:", url.value);
});

说明:

  • 每次执行 changeIdid 更新后 url 会重新计算;
  • watchEffect 会自动监听依赖并立即执行;
  • 若需要拿到“新旧值”,应使用 watch 而不是 watchEffect

useFetch 封装示例

javascript
import { isRef, ref, unref, watchEffect } from "vue";

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);

  async function doFetch() {
    data.value = null;
    error.value = null;
    loading.value = true;

    try {
      const urlVal = unref(url);
      const res = await fetch(urlVal);
      data.value = await res.json();
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  }

  if (isRef(url)) {
    watchEffect(doFetch);
  } else {
    doFetch();
  }

  return { data, error, loading, doFetch };
}

页面渲染建议

建议将状态渲染统一为公共组件,避免页面重复判断逻辑:

html
<div v-if="loading">loading...</div>
<div v-else-if="error">错误:xxxx(可重试)</div>
<div v-else>数据:xxxx</div>

在企业项目中,这类基础能力统一后,能够显著降低重复代码并提升交互一致性。

与手动实现的对比

维度页面内手写请求逻辑统一 useFetch 封装
状态管理每个页面各自定义 loading/error 变量由封装统一返回,命名一致
依赖切换需要手动在切换回调里重新发请求watchEffect 自动感知依赖变化
重试逻辑容易遗漏或各页面实现不一致复用同一套 doFetch,重试即重新调用
可测试性逻辑分散,难以单独测试可对 useFetch 单独编写单元测试

回归检查清单

  • 切换选项卡(更新依赖 id)后能正确重新发起请求,且旧请求结果不会覆盖新请求结果;
  • 请求失败时 error 状态正确展示,重试按钮可正常触发 doFetch 重新请求;
  • 快速连续切换选项卡时不出现请求堆积或界面闪烁「loading -> 数据 -> loading」的异常跳变;
  • url 传入非 ref 的普通值时(一次性请求场景),逻辑仍能正确执行且不会误触发 watchEffect
  • 组件销毁时无残留的未处理 Promise 导致的报错或内存泄漏提示。

常见疑问

  1. 为什么不直接用 watch 而用 watchEffect
    watchEffect 会自动收集依赖,不需要手动列出监听的字段,对于「依赖变化即整体重新请求」的场景更简洁;如果后续需要对比新旧值做差异化处理,再改为 watch 也不冲突。
  2. useFetch 是否可以扩展支持缓存?
    可以,常见做法是按 url 作为 key 缓存最近一次结果,命中缓存时先展示旧数据再静默更新,避免每次切换都出现短暂空白,具体策略需结合业务对「数据实时性」的要求取舍。