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

React useEffect场景下exhaustive-deps规则提示是否正确?

现有写法的潜在问题

你现在的实现不是ESLint误报,确实存在可复现的闭包陈旧值bug,最典型的场景:

  • 当用户从A的个人主页快速跳转到B的个人主页时,effect触发时捕获的postedResources还是A用户主页的已加载状态(非null),此时B用户的首屏加载不会触发全页加载动画,页面会出现一段无任何提示的空白期,直到接口返回内容才会渲染。
  • 如果后续给列表加筛选、排序、标签过滤等会重置列表的功能,只要触发重置时profileUser和postedResourcesCurrentPage两个依赖没有变化,effect里读到的永远是旧的postedResources值,会出现加载态不显示的问题。

你提到把postedResources加入依赖会触发无限循环,这个问题的根源不是规则要求错了,而是你的状态更新逻辑和触发条件没有对齐:effect每次执行完都会调用setPostedResources传入接口返回的全新数组引用,即使内容完全一致,引用变化也会触发effect重跑,重跑又会再次触发状态更新,形成死循环。

修复方案

根据你的业务场景(分页展示用户发布的资源),有两种成熟的修复方式,不需要禁用ESLint规则就能解决问题:

方案1:逻辑简化(推荐,适合无限滚动/无跨页跳转的场景)

你判断是否显示全页加载态的核心逻辑,本质是识别「当前是不是当前用户的第一页加载」,完全不需要依赖postedResources状态,直接基于页码判断即可,从根源上避开闭包和循环问题:

// 切换用户时重置分页状态
useEffect(() => {
  setPostedResources(null);
  setPostedResourcesCurrentPage(1);
  setPostedResourcesTotalCount(0);
  setPostedResourcesPageCount(0);
}, [profileUser]);

useEffect(() => {
  async function loadPostedResources() {
    setPostedResourcesLoadingError(false);
    // 页码为1即首屏加载,显示全页loading;翻页加载不触发全页loading
    if (postedResourcesCurrentPage === 1) {
      setPostedResourcesLoading(true);
    }
    try {
      const response = await TutHubApi.fetchResourcesPostedByUser(profileUser._id, postedResourcesCurrentPage);
      // 分页场景用函数式更新拼接数据,避免全量替换丢失已加载的前页内容
      setPostedResources(prev => 
        postedResourcesCurrentPage === 1 
          ? response.resources 
          : [...(prev || []), ...response.resources]
      );
      setPostedResourcesTotalCount(response.totalCount);
      setPostedResourcesPageCount(response.pageCount);
    } catch (error) {
      console.error(error);
      setPostedResourcesLoadingError(true);
    } finally {
      setPostedResourcesLoading(false);
    }
  }
  loadPostedResources();
}, [profileUser, postedResourcesCurrentPage]);

这个写法逻辑更直观,没有额外的状态同步成本,只要保证切换用户时重置页码和列表状态,就不会出现问题。

方案2:Ref同步值(适合有跳页、筛选等复杂列表逻辑的场景)

如果你确实需要读取最新的postedResources值做复杂判断,又不想把它加入effect依赖,可以用useRef同步最新的状态值,ref的变化不会触发重渲染,也不需要加入依赖数组,同时能避免闭包拿旧值的问题:

const [postedResources, setPostedResources] = useState<Resource[] | null>(null);
// 用ref同步列表的最新值
const postedResourcesRef = useRef(postedResources);
useEffect(() => {
  postedResourcesRef.current = postedResources;
}, [postedResources]);

// 切换用户时重置分页状态
useEffect(() => {
  setPostedResources(null);
  setPostedResourcesCurrentPage(1);
  setPostedResourcesTotalCount(0);
  setPostedResourcesPageCount(0);
}, [profileUser]);

useEffect(() => {
  async function loadPostedResources() {
    setPostedResourcesLoadingError(false);
    // 从ref读取最新的列表状态,不会有闭包陈旧问题
    if (!postedResourcesRef.current) {
      setPostedResourcesLoading(true);
    }
    try {
      const response = await TutHubApi.fetchResourcesPostedByUser(profileUser._id, postedResourcesCurrentPage);
      setPostedResources(prev => 
        postedResourcesCurrentPage === 1 
          ? response.resources 
          : [...(prev || []), ...response.resources]
      );
      setPostedResourcesTotalCount(response.totalCount);
      setPostedResourcesPageCount(response.pageCount);
    } catch (error) {
      console.error(error);
      setPostedResourcesLoadingError(true);
    } finally {
      setPostedResourcesLoading(false);
    }
  }
  loadPostedResources();
}, [profileUser, postedResourcesCurrentPage]);

注意:不要直接加注释禁用exhaustive-deps规则,绝大多数场景下这个规则的报错都是在提示你代码里存在闭包风险,直接禁用只是掩盖问题,后续迭代很容易出现状态不一致的隐蔽bug。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:21:27