为什么开发者写 useEffect 的次数比以前少多了

为什么开发者写 useEffect 的次数比以前少多了

现代 React 模式正在取代那个曾经感觉是万能方案的 Hook

那个解决了一切问题的 Hook(但其实并没有)

当你第一次学习 React 时, useEffect 感觉很神奇。需要同步一些状态?有一个专门的 hook。想要获取数据?Hook。把两个 props 合并成一个?又一个 hook。看了一些教程之后,感觉就像是 useEffect 几乎是解决所有问题的答案。然后你构建了更大的项目。

今天是 2026 年 7 月 15 日,React 社区正在重新审视 useEffect。根据一位在大型应用程序上工作的高级开发者 Alejandro 最近的一篇文章,这个曾经感觉必不可少的模式,他现在使用的频率比他早年时减少了大约 80%。原因并不是说 useEffect 很糟糕——而是开发者用它解决的大多数问题都有更简单的解决方案。

这在当下很重要,因为越来越清楚的是,学好 React 意味着要学习 何时不 使用它最著名的工具。

useEffect 实际上是用来做什么的

让我们从官方说法开始。React 的文档将 effect 描述为“将你的组件与外部系统同步”的一种方式。关键词是: 外部。React 本身之外的事物——比如网络请求、WebSocket 连接、计时器、浏览器 API、订阅或第三方库。

如果你的 effect 没有与 React 之外的东西通信,很有可能你其实并不需要它。

你可能做错的五件事

在 effect 中派生值

最常见的模式之一是使用 useEffect 将 props 或状态组合成一个新值。例如:

const [fullName, setFullName] = useState("");
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

这可行,但 React 会渲染两次:一次使用原始的空状态,然后 effect 运行,状态改变,React 再次渲染。没有理由进行那次额外的渲染。

相反,只需在渲染期间计算该值:

const fullName = `${firstName} ${lastName}`;

更简单,更快,更少的渲染。

将 props 复制到状态中

另一个常见的模式是将 prop 同步到局部状态中:

const [user, setUser] = useState(props.user);
useEffect(() => {
  setUser(props.user);
}, [props.user]);

这创建了两个单一事实来源。通常最后是一个被更新了,而另一个没有。除非你明确需要一个本地可编辑的副本(这很少见),否则直接使用 prop 即可。更简单的方法:

function Profile({ user }) {
  return <h2>{user.name}</h2>;
}

单一事实来源。更容易调试。

在 effect 中过滤或转换列表

你可能见过这样的代码:在 effect 内部过滤列表,并将结果存储在状态中:

const [filteredUsers, setFilteredUsers] = useState([]);
useEffect(() => {
  setFilteredUsers(users.filter(user => user.active));
}, [users]);

同样,这是不必要的状态。只需在渲染期间计算它:

const filteredUsers = users.filter(user => user.active);

如果计算成本很高,有一个专门的 hook——但它不是 useEffect。它是 useMemo,它会记忆结果,因此只有在依赖项更改时才会重新计算:

const filteredUsers = useMemo(() => {
  return users.filter(user => user.active);
}, [users]);

但是请记住: useMemo 是一种 优化,而不是取代对代码的思考。

使用 effect 进行调试

有一个地方临时使用 effect 是有意义的:调试。每当值更改时记录日志确实很有用:

useEffect(() => {
  console.log(user);
}, [user]);

但是这些应该在合并代码之前删除。

用旧方法获取数据

几年前,几乎每个 React 项目都有这种模式:

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

这可行,但它做的并不多。没有错误处理,没有加载状态,没有重试逻辑,如果组件挂载两次也没有去重。大多数团队最终都自己构建了所有这些。

现在有了更好的选择。像 TanStack Query 和 SWR 这样的库会自动处理缓存、重试、后台重新获取、加载状态、错误状态和去重。你不需要编写 effect,而是使用一个 hook:

const { data, isLoading } = useQuery({
  queryKey: ["users"],
  queryFn: getUsers
});

代码少得多。错误少得多。更好的开发者体验。

当 Effect 隐藏真实问题时

随着时间的推移,Alejandro 注意到了一个模式:当一个组件有很多 effect 时,它通常做得太多了。也许它在一个组件内部获取数据、过滤、排序、格式化、验证并处理事件。这不是 effect 的问题——这是架构问题。

通常,将职责拆分到较小的 hook 或组件中会自动消除一半的 effect。

当你确实需要 useEffect 时

所有这些并不意味着“永远不要使用 useEffect。”有很多合理的理由:

WebSocket 连接: 你需要在组件挂载时打开连接,并在卸载时关闭连接。这正是 effect 的用途。

计时器: 如果你需要每 5 秒轮询一次更新, setInterval 在带有适当清理的 effect 内部是有意义的。

浏览器 API: 监听 window 的 resize 事件或与 localStorage 同步是副作用。

第三方库: 初始化图表库或分析 SDK——这些需要在组件挂载时运行。

这些正是 effect 被设计用来处理的场景:与 React 之外的事物同步。

改变一切的问题

在编写 effect 之前,Alejandro 会问自己一件事:“我是在与外部系统同步,还是在弥补我的组件设计的不足?”

他说,单单这个问题就从他的项目中去除了大量不必要的代码。

结论

useEffect 并不糟糕。它只是比大多数 React hook 更容易被滥用。现代 React 开发者倾向于使用派生值,保持 props 原样,使用查询库来处理服务器状态,并将 effect 留给那些确实需要它们的事情。结果是渲染次数更少,要管理的状态更少,错误更少,而且组件在几个月后更容易理解。

优点

  • 减少不必要的重新渲染并提高性能
  • 通过避免多余的状态和 effect 来简化代码
  • 副作用较少的组件更容易调试和维护
  • 查询库自动处理复杂的数据获取逻辑
  • 当 effect 凸显设计问题时能带来更好的组件架构
  • 状态越少意味着错误藏身的地方越少

缺点

  • 需要学习何时使用替代方案(useMemo、自定义 hook、查询库)
  • 习惯于重度 effect 模式的开发者可能需要改变他们的习惯
  • 一些遗留项目严重依赖 useEffect 模式,无法在一夜之间重构
  • 并非每个团队都采用了像 TanStack Query 这样的库
  • 如果不谨慎处理,内联计算可能会降低可读性

注意事项

本文具有教育意义,并解释了社区文章中讨论的现代 React 模式。代码示例仅作说明之用——如果你在真实项目中使用它们,请将占位符值替换为你的实际 API 端点和逻辑。在将模式用于生产代码之前,始终对照原始出处验证它们。React 和生态系统发展迅速;请查看官方 React 文档和库文档(TanStack Query、SWR)以获取最新指南。

常见问题解答

  • 什么时候应该使用 useEffect 而不是派生状态? ——仅当你与外部系统(API、计时器、浏览器事件)同步时,才使用 useEffect 。如果你只是转换现有数据,请在渲染期间派生它。

  • useMemo 在计算方面比 useEffect 更好吗?useMemo 能优化昂贵的计算,但要深思熟虑地使用它。大多数计算速度都足够快,可以在每次渲染时计算——仅在性能分析表明有影响时才进行记忆化。

  • 我应该替换所有的 useEffect 数据获取吗? ——像 TanStack Query 这样的库比 useEffect强大得多,但迁移大型代码库需要时间。从新功能开始,然后逐步重构。

  • TanStack Query 和 SWR 之间有什么区别? ——两者都是处理缓存和重新获取的查询库。TanStack Query 功能更全面;SWR 更简单、更轻量。根据你项目的需求进行选择。

  • 我还可以使用 useEffect 进行调试吗? ——可以,但是在合并代码之前删除调试 effect。使用浏览器的 DevTools 进行生产环境调试。

  • 我如何知道我的组件是否做得太多? ——如果它有两个或三个以上的 effect,或者如果 effect 依赖于许多不同的东西,请考虑将其拆分为更小的组件或自定义 hook。

  • 避免使用 useEffect 会让 React 变得更难学吗? ——并不真正如此——它意味着更好地学习 React 的核心模型。了解 何时不 使用某个功能通常可以澄清它的实际用途。

  • 那 WebSockets 和浏览器 API 呢——它们总是需要 useEffect 吗? ——是的,如果你在管理 React 之外的某些事物的生命周期,带有适当清理的 useEffect 就是正确的工具。

标签

#react #useeffect #javascript #webdev #frontend #reacthooks #modernreact #bestpractices

Free field guide

Incident Response: First Hour

A calm, evidence-preserving checklist for establishing control, bounding impact, communicating clearly, and containing an incident safely.