别再直接写异步请求了!React 初学者最容易犯的错误

592 阅读3分钟

在 React 开发中,刚入门的新手都会遇到这样一个问题:

“为什么我只写了一次网络请求代码,结果却反复执行?”

这个问题背后其实隐藏着一个非常常见的误区:把异步请求直接写在组件函数里。 这会导致页面每次重新渲染时都重复发起请求,不仅浪费资源,还可能引发 bug。

今天我们就来详细讲解这个错误,并教你如何用 useEffect 正确地发起一次请求。

场景模拟:一个简单的计数器组件

我们来看一个例子:


import { useState } from "react";

function App() {
  const [num, setNum] = useState(0);

  // 模拟异步请求
  fetchData().then(data => {
    setNum(data);
  });

  return (
    <div onClick={() => setNum(num + 1)}>
      当前数值:{num}
    </div>
  );
}

// 模拟网络请求
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => resolve(666), 1000);
  });
}

export default App;

看起来是不是很熟悉?这段代码的功能也很简单: 页面显示一个数字; 初始值是 0; 然后从服务器获取数据(模拟返回 666); 点击页面可以让数字加 1。 但你有没有发现一个问题?

错误行为:每次渲染都在请求!

第一步:页面加载 → 发起请求; 第二步:数据返回 → 设置 num = 666 → 组件重新渲染; 第三步:重新渲染 → 又执行一遍 fetchData() → 再次发起请求; 第四步:再次点击页面 → 更新状态 → 组件又重新渲染 → 又发起一次请求…… 这样就形成了一个“无限循环”的请求过程!

为什么会这样?

因为 React 的函数组件本质上是一个函数,它会在每次状态更新后重新执行。 所以如果你把异步请求直接写在组件函数里: fetchData().then(...);

那每次组件重新渲染时,都会重新执行这一行代码,从而导致请求被反复调用。

正确做法:使用 useEffect 控制副作用执行时机

React 提供了一个专门处理副作用的 Hook —— useEffect

我们可以这样改写上面的代码:

import { useState, useEffect } from "react";

function App() {
  const [num, setNum] = useState(0);

  useEffect(() => {
    fetchData().then(data => {
      setNum(data);
    });
  }, []); // 注意这里有个空数组 []

  return (
    <div onClick={() => setNum(num + 1)}>
      当前数值:{num}
    </div>
  );
}

function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => resolve(666), 1000);
  });
}

export default App;

这样就能保证:

  • 只在组件第一次加载时请求一次
  • 后续更新不会再重复请求,避免无限循环

为什么要加上 []

  • [] 是 useEffect 的依赖项数组;
  • 如果是空数组,表示这个副作用只在组件第一次加载时执行一次;
  • 后续不管怎么点击、更新状态,都不会再执行这个副作用。 这样一来,就避免了重复请求的问题。

小贴士:什么是副作用?

在 React 中,“副作用”指的是那些不在渲染过程中直接完成的操作,例如:

  • 发起网络请求
  • 添加事件监听器
  • 操作 DOM
  • 设置定时器

这些操作会影响外部世界或产生延迟效果,不适合在渲染过程中随意执行。

React 提供了 useEffect 来专门管理这些副作用,让我们可以精确控制它们的执行时机和清理方式。

对比效果:两种写法的区别

不使用 useEffect使用 useEffect([], ...)
每次组件重新渲染都会执行请求只在组件首次加载时执行一次
数据返回后触发更新,再次执行请求数据返回后触发更新,不再重复请求
导致请求无限循环请求只执行一次,更合理高效