White Meta
登录Admin
Language
中文EN
Theme: ...

© 2026 White Meta

回到顶部

Back to topics

This article does not have an English body yet. Showing the Chinese version below.

前端为什么不能直接在 useEffect 里 fetch?

很多前端项目里,获取数据的代码都长这样:

useEffect(() => {
  fetch("/api/books")
    .then((res) => res.json())
    .then(setBooks);
}, []);

它看起来简单直接,所以常常被当成“标准起手式”。但如果你真的把业务系统做大,就会发现这类写法只适合 demo,不适合长期维护。

问题不在于 fetch 这个 API 本身,而在于“把请求直接塞进组件副作用里”会让很多本该被认真处理的事情,悄悄变成遗漏项。

第一层问题:你拿到的从来不只是 data

一次请求真正带来的,不只有“成功后的数据”,还有一整串派生状态:

  • 现在是不是正在加载
  • 请求有没有失败
  • 失败原因是什么
  • 之前的数据还能不能先展示
  • 这个请求是不是已经过期

如果这些状态都不管,页面在用户眼里就会很粗糙。

最典型的例子就是加载态。没有 loading,用户看到的不是“正在获取数据”,而是“页面像卡住了一样毫无反应”。一旦请求失败,如果没有 error 分支,用户又会自然地把锅甩给前端。

所以问题不是“能不能请求成功”,而是“请求过程中用户究竟经历了什么”。

第二层问题:竞态会让页面显示错数据

竞态问题经常出现在搜索、筛选、分页这些高频交互里。

比如用户快速从第 1 页翻到第 10 页。页面理论上应该显示第 10 页的数据,但网络响应顺序并不等于请求发出顺序。如果第 3 页的响应更晚才回来,它完全可能把第 10 页的结果覆盖掉。

这就是竞态:用户最后一次操作是对的,页面最终展示却是错的。

可以把它想象成理发店同时接到两个互相冲突的需求。你先说“烫大波浪”,后来说“剃光头”,最后成品是什么,取决于谁先动手,而不是你先说了什么。

在代码里,这种问题不会靠“多写几个 if”自然消失。只要请求之间存在重叠,状态就必须被系统化管理。

第三层问题:你迟早会碰到缓存

再看一个很常见的业务动作:

  1. 用户进入列表页
  2. 点进详情页
  3. 再返回列表页

如果你只是“组件挂载就请求”,那么列表页很可能又重新请求一次。技术上当然能跑,但体验并不好:用户明明刚看过的数据,却还得再等一遍。

这时候你就会开始思考:

  • 哪些数据可以缓存
  • 缓存多久算合理
  • 什么时候应该静默刷新
  • 什么时候应该直接阻塞并重新加载

这已经不是单纯的“发请求”了,而是异步状态管理。

例如有些数据实时性要求很低,完全可以先展示缓存,再悄悄请求最新数据;如果新旧数据一致,用户甚至不需要知道背后发生过一次刷新。这样就能在“数据新鲜度”和“交互流畅度”之间找到平衡。

为什么“统一 loading”也不够

有些团队会说,那我做个全局请求拦截器不就行了?请求开始就转圈,请求失败就弹错误提示。

这当然比什么都不处理强,但依然不够细。

因为不同场景需要的反馈方式完全不同:

  • 全页首屏加载,可能适合骨架屏
  • 表格翻页,可能适合保留旧数据并做轻微置灰
  • “加载更多”按钮,可能适合只让按钮进入 pending 状态
  • 提交表单,可能适合禁用提交并显示进度

统一 loading 解决的是“有没有反馈”,却解决不了“反馈是否贴合场景”。

真正要管理的,是异步状态

很多人以为数据请求的核心是“把接口调通”。其实一旦进入真实项目,核心会变成:

  • 如何表达请求生命周期
  • 如何避免过期请求污染界面
  • 如何缓存已经拿到的数据
  • 如何减少不必要的等待

这也是为什么 React 官方一直不鼓励把复杂数据获取逻辑长期堆在 useEffect 里。useEffect 更适合描述副作用本身,而不是承担整套异步状态管理职责。

更像工程化方案的做法

像 TanStack Query 这类工具,本质上不是“帮你发请求”,而是帮你管理请求带来的状态系统。

它会把很多前端迟早要面对的问题直接变成能力:

  • isLoading、isError 这类派生状态开箱即用
  • 基于 queryKey 的缓存天然存在
  • 请求参数变化时,旧数据和新数据如何衔接更容易控制
  • 预加载、重试、失效、乐观更新这些能力也能逐步接上

所以它真正替代的,不是 fetch 或 axios,而是你手写的那一大堆零散状态与边界处理。

结语

前端当然可以直接在 useEffect 里 fetch,就像你也可以把所有代码都写进一个文件里。

问题从来不是“能不能跑”,而是“跑多久以后会开始难受”。

当页面只是一段 demo,这种写法轻便、省事、成本低。但一旦你要面对加载态、错误态、竞态、缓存、预加载、重试这些真实问题,你就会发现:你要管理的从来不是一次请求,而是一整套异步状态。

理解这一点,才算真正从“会调接口”走向“会设计数据获取方案”。