别再用 useEffect 写请求了,试试请求策略方案

⚠️ 【待 review】

别再用 useEffect 写请求了,试试请求策略方案


我花了三年,才发现自己在「装忙」

大概三年前,我写了一个管理后台。

客户要一个用户列表,带搜索、带分页。我心想,这还不简单?React + useEffect + fetch,三件套走起。

结果呢?代码越写越多。一个列表页,光请求逻辑就写了 60 行。还不算 loading、error、空数据的状态处理。

最后交上去,老板看了一眼:"这个页面怎么加载这么慢?"

我说:"因为每次输入搜索词都要重新请求。"

老板说:"那你不会防抖吗?"

我:"......会。"

我确实会。但每次都要手写 debounce、手动清理 effect、手动处理竞态问题------真的很累。

直到我遇到了 alova。一个让我怀疑自己过去三年是不是在「装忙」的请求库。


Before:Effect 地狱

先给你看看我之前的代码长什么样。

这是一个带搜索的用户列表页:

tsx 复制代码
// 🔴 Before:80 行的 useEffect 地狱
import { useState, useEffect, useCallback } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [keyword, setKeyword] = useState('');
  const [page, setPage] = useState(1);

  // 获取用户列表
  const fetchUsers = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(
        `/api/users?keyword=${keyword}&page=${page}&pageSize=10`
      );
      if (!res.ok) throw new Error('请求失败');
      const data = await res.json();
      setUsers(data.list);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [keyword, page]);

  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

  // 防抖搜索
  useEffect(() => {
    const timer = setTimeout(() => {
      if (keyword) fetchUsers();
    }, 500);
    return () => clearTimeout(timer);
  }, [keyword, fetchUsers]);

  // 竞态处理(手动标记)
  useEffect(() => {
    let cancelled = false;
    const doFetch = async () => {
      const res = await fetch(`/api/users?keyword=${keyword}&page=${page}`);
      const data = await res.json();
      if (!cancelled) setUsers(data.list);
    };
    doFetch();
    return () => { cancelled = true; };
  }, [keyword, page]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>出错了:{error}</div>;

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <ul>
        {users.map(u => <li key={u.id}>{u.name}</li>)
</ul>
    </div>
  );
}

这段代码的问题在哪?

  1. 手动状态太多 --- loading、error、data 三个状态全部自己声明、自己维护
  2. 防抖自己写 --- 每次都要 setTimeout + clearTimeout,一个不小心就漏了
  3. 竞态要自己处理 --- cancelled 标志位,写 10 次错 3 次
  4. 依赖地狱 --- useCallback + useEffect 的依赖链,改一个变量要排查半天

我在好几个项目里写过类似的代码。每次都告诉自己:"下次重构就好了。"

但下次,还是这样写。


After:alova 的请求策略方案

直到有人把 alova 甩到我脸上。

然后我发现......原来这些破事,早就不用自己干了。

tsx 复制代码
// 🟢 After:alova 请求策略,15 行搞定
import { useRequest, useWatcher } from 'alova/client';
import { alovaInstance } from './api';

function UserList() {
  const [keyword, setKeyword] = useState('');

  // 列表请求 + 分页
  const { loading, data: users = [], error } = useRequest(
    () => alovaInstance.Get('/api/users', {
      params: { keyword, page: 1, pageSize: 10 }
    }),
    { initialData: [] }
  );

  // 搜索:监听 keyword 变化,自动带防抖
  const { data: searchResult = [] } = useWatcher(
    () => alovaInstance.Get('/api/users', {
      params: { keyword, page: 1, pageSize: 10 }
    }),
    [keyword],
    {
      debounce: 500,   // ✅ 内置防抖,一行搞定
      immediate: false // 初始化不请求
    }
  );

  if (loading) return <div>加载中...</div>;
  if (error) return <div>出错了:{error.message}</div>;

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <ul>
        {users.map(u => <li key={u.id}>{u.name}</li>)}
      </ul>
    </div>
  );
}

这不是魔法,这是设计。

alova 的核心理念是:把请求从「实现」变成「声明」

你之前手写的 现在 alova 帮你做
useState 声明 loading/data/error useRequest 直接返回响应式状态
useEffect + useCallback 依赖链 声明性监听,依赖自动管理
setTimeout 手写防抖 debounce: 500 一行配置
cancelled 标志位处理竞态 abortLast: true 自动中断上一次
手动拼接 URL 参数 params 对象自动处理

这些不是"锦上添花"的功能,而是每个前端每天都在重复踩的坑。alova 一次性帮你填平了。


什么是「请求策略」?

你可能会问:alova 到底做了什么,让代码变得这么短?

答案是 请求策略化

传统写法里,你写的是"怎么请求"(用 fetch 还是 axios、参数怎么拼、状态怎么存)。而 alova 让你写的是"请求什么"------剩下的它自己搞定。

alova 的策略体系就是干这个的:

策略 Hook 你告诉它 它帮你做
useRequest 请求什么 自动管理 loading/data/error
useWatcher 监听什么状态 自动防抖、自动重新请求、自动处理竞态
usePagination 分页参数 自动管理页码、预加载、乐观更新
useForm 表单数据 草稿持久化、自动提交、重置
useAutoRequest 轮询配置 自动轮询、焦点刷新、重连刷新
useCaptcha 手机号 验证码倒计时、自动发送

每个 Hook 对应一个你大概率写过无数遍 的场景。不是让你少写几行,是让你直接不写


从效果出发的反思

换了 alova 之后,我的项目发生了几个变化:

  1. 代码量减少 60% --- 一个页面平均从 150 行降到 60 行
  2. Bug 大幅减少 --- 手写 debounce 的边界条件、竞态的条件判断,全没了
  3. 新人上手更快 --- 不用教"你要注意竞态问题",直接说"这个用 useWatcher"
  4. Review 时间缩短 --- 以前 review 请求代码要一行行看 useEffect 的依赖,现在一眼扫过去就懂了

你可能会说:这不就是封装了一下吗?我自己也能封装。

对,你确实能。

但问题是------你每一次都要花时间封装,封装完了还要维护,换了项目还要重新封装。而 alova 把这些事做到了极致:开箱即用的策略、内置的缓存、服务端也能用、类型完美推断

这不是语法糖,是范式升级。


反哺社区

这篇文章不是广告。

alova 本身是一个开源项目,社区活跃,文档齐全。它的请求策略方案也不是我吹的------你去看文档,每个策略的源码都在 GitHub 上,透明公开。

我个人从 alova 受益良多,所以想把这种思路分享给更多前端开发者。

别再用 useEffect 写请求了。你值得拥有更好的方案。

如果你也想试试:

bash 复制代码
# 安装 alova
npm install alova
# 选择一个适配器
npm install alova/fetch  # 或 axios/alova、uniapp-adapter 等

然后就可以写下你第一个策略化请求了。


本文使用 alova v3。查看官方文档:alova.js.org