You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React开发如何避免修改HTML内容时useEffect不必要触发?

提问内容

我正在学习React,创建了一个展示人员列表和数量的简单组件,代码如下:

const getPeople = async (pageNumber) => {
  return await axios.get("https://randomuser.me/api", 
   { params: {page: pageNumber, results: 8 }})
  .then((res) => {
    console.log(res.data.results);
    return res.data;
  })
  .catch((err) => {
    throw new Error("Smth went wrong...");
  });
};

const People = () => {
  const [pageNumber, setPageNumber] = useState(1);
  const [error, setError] = useState(null);
  const [people, setPeople] = useState([]);

  const clickHandler = () => {
    setPageNumber((prev) => (prev = prev + 1));
  };

  useEffect(() => {
    async function Get() {
      try {
        const { results } = await getPeople(pageNumber);
        setPeople((prevState) => [...prevState, ...results]);
      } catch (err) {
        setError(err.message);
      }
    }

    Get();
  }, [pageNumber]);

  return (
    <div>
      {error && <h3>{error}</h3>}
      <h2>{people.length}</h2>
      <div className="person-grid">
        {people.map(({ name, picture }) => (
          <div className="person-card">
            <img src={picture.large} alt={name.first} />
            <p>{name.first + " " + name.last}</p>
          </div>
        ))}
      </div>

      <div className="button-wrapperr">
        <button className="load-button" onClick={clickHandler}>
          Load more.
        </button>
      </div>
    </div>
  );
};

组件运行效果如下:
组件运行效果
点击加载更多按钮可以获取新数据并追加到数组中,这部分功能运行正常。但我遇到了一个问题:组件首次挂载后数组中已有8条数据,当我修改HTML相关内容比如某个元素的className时,即使useEffect的依赖项pageNumber没有发生变化,useEffect也会被触发执行,导致数组被追加新数据变成16条。
我的问题是如何避免useEffect在修改HTML内容时被触发,我希望它仅在pageNumber变化时执行,而非修改元素className这类操作时触发。
备注:该问题是热重载导致的,修改内容后刷新页面运行就会恢复正常,但我不希望每次修改className都刷新页面丢失当前状态。


问题原因

这个问题是开发环境热重载(HMR)的特性导致的:热重载修改组件代码后会保留组件现有state,但会重新执行所有副作用。你修改className后组件触发热重载,pageNumber状态还保留为1,useEffect检测到依赖项pageNumber的值存在,就会重新执行拉取逻辑,再次请求第1页的8条数据追加到原有数组,就出现了总条数变16的情况。这个问题仅在开发环境出现,不会影响生产环境的运行。

解决方案

方案1:新增已拉取页码判断,最简单直接

在请求逻辑前判断当前页是否已经拉取过,已经拉取过就直接跳过请求,适配你当前的分页逻辑:

useEffect(() => {
  async function Get() {
    try {
      // 新增判断:每页8条,已拉取总条数大于等于当前页对应最大条数时跳过请求
      const isPageFetched = people.length >= pageNumber * 8;
      if (isPageFetched) return;
      const { results } = await getPeople(pageNumber);
      setPeople((prevState) => [...prevState, ...results]);
    } catch (err) {
      setError(err.message);
    }
  }
  Get();
}, [pageNumber, people.length]);

方案2:单独维护已拉取页码集合,适配更复杂的场景

如果后续可能有删除单条数据、调整每页条数的需求,可以单独用state存储已经拉取过的页码,避免判断逻辑失效:

// 新增state存储已拉取的页码
const [fetchedPages, setFetchedPages] = useState(new Set());

useEffect(() => {
  async function Get() {
    try {
      // 当前页已经拉取过就直接跳过
      if (fetchedPages.has(pageNumber)) return;
      const { results } = await getPeople(pageNumber);
      setPeople((prevState) => [...prevState, ...results]);
      // 拉取成功后把当前页码加入已拉取集合
      setFetchedPages(prev => new Set(prev).add(pageNumber));
    } catch (err) {
      setError(err.message);
    }
  }
  Get();
}, [pageNumber, fetchedPages]);

方案3:使用数据请求库,从根源避免重复请求

如果项目可以引入第三方库,直接使用SWR或者React Query这类数据请求工具,天然支持相同参数请求的缓存、去重,不需要手动写判断逻辑,还能自动处理加载、错误、分页缓存等通用需求。


内容的提问来源于stack exchange,提问作者Notch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:54:03