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

为何第一段useEffect导致组件渲染失败?两段代码差异解析

问题描述

我在项目中使用第一段useEffect代码时,组件无法正确渲染:

useEffect(() => {
  setFavoriteContact((item) => {
    const favorites = window?.localStorage?.getItem("favorites")
      ? JSON.parse(`${localStorage.getItem("favorites")}`)
      : [];

    const favoritas = [...item, ...favorites];

    contacts.forEach((item) => {
      const firstLetter = item.last_name.charAt(0).toUpperCase();

      if (favoritas.includes(`${item.last_name} ${item.first_name}`)) {
        if (!favoriteContactGroup[firstLetter]) {
          favoriteContactGroup[firstLetter] = [];
        }
        favoriteContactGroup[firstLetter].push(item);
      } else {
        if (!normalContactGroup[firstLetter]) {
          normalContactGroup[firstLetter] = [];
        }
        normalContactGroup[firstLetter].push(item);
      }
    });

    return favoritas;
  });
}, []);

调整为第二段代码后,组件可正常渲染,但存在数据展示延迟(无法立即将收藏联系人放到正确位置):

useEffect(() => {
  const favorites = window?.localStorage?.getItem("favorites")
    ? JSON.parse(`${localStorage.getItem("favorites")}`)
    : [];
  setFavoriteContact((item) => [...item, ...favorites]);
}, []);

contacts.forEach((item) => {
  const firstLetter = item.last_name.charAt(0).toUpperCase();

  if (favoriteContact.includes(`${item.last_name} ${item.first_name}`)) {
    if (!favoriteContactGroup[firstLetter]) {
      favoriteContactGroup[firstLetter] = [];
    }
    favoriteContactGroup[firstLetter].push(item);
  } else {
    if (!normalContactGroup[firstLetter]) {
      normalContactGroup[firstLetter] = [];
    }
    normalContactGroup[firstLetter].push(item);
  }
});

我的疑惑:为何第一段代码失效而第二段有效?控制台打印normalContactGroup和favoriteContactGroup的值是正确的,但组件未触发重渲染?

补充信息

  • favoriteContactGroup和normalContactGroup是普通空对象,初始定义:
const normalContactGroup: { [key: string]: Contact[] } = {};
const favoriteContactGroup: { [key: string]: Contact[] } = {};
  • 后端返回的联系人数据结构示例:
const contactArray: Contact[] = [
  {
    _id: "6561a98995095b8c3c8b38ca",
    contact_id: "6561a98995095b8c3c8b38c6",
    first_name: "Poppy",
    last_name: "Abba",
    default_number: "980891",
    phone_numbers: 2,
    createdAt: "2023-11-25T08:00:09.442Z",
    updatedAt: "2023-11-25T08:00:09.442Z",
    __v: 0,
  },
  {
    _id: "6561a97695095b8c3c8b38c4",
    contact_id: "6561a97695095b8c3c8b38c0",
    first_name: "Berkley",
    last_name: "Arkham",
    default_number: "89701234",
    phone_numbers: 2,
    createdAt: "2023-11-25T07:59:50.618Z",
    updatedAt: "2023-11-25T07:59:50.618Z",
    __v: 0,
  },
];
  • 处理后分组对象结构示例:
{
  "A": [
    {
      "_id": "6561a98995095b8c3c8b38ca",
      "contact_id": "6561a98995095b8c3c8b38c6",
      "first_name": "Poppy",
      "last_name": "Abba",
      "default_number": "980891",
      "phone_numbers": 2,
      "createdAt": "2023-11-25T08:00:09.442Z",
      "updatedAt": "2023-11-25T08:00:09.442Z",
      "__v": 0
    },
    {
      "_id": "6561a97695095b8c3c8b38c4",
      "contact_id": "6561a97695095b8c3c8b38c0",
      "first_name": "Berkley",
      "last_name": "Arkham",
      "default_number": "89701234",
      "phone_numbers": 2,
      "createdAt": "2023-11-25T07:59:50.618Z",
      "updatedAt": "2023-11-25T07:59:50.618Z",
      "__v": 0
    }
  ]
}

问题原因与解决方案

第一段代码失效的核心原因

  1. 普通变量无法触发重渲染:
    favoriteContactGroup和normalContactGroup是普通JS变量,不是React状态。React只有在状态/Props发生可检测的引用变化时才会触发重渲染,普通变量的修改不会被React感知,即便控制台打印值正确,UI也不会更新。
  2. 状态更新函数的闭包限制:
    你在setFavoriteContact的更新函数内部修改分组变量,而这个更新函数是异步执行的闭包,此时修改的变量属于组件初始渲染的闭包上下文,后续组件无法捕获到这些变化。

第二段代码有效的原因

第二段把分组逻辑放到了组件渲染阶段(useEffect之外):第一次渲染时,分组逻辑用的是favoriteContact的初始值;当useEffect里的setFavoriteContact异步更新状态后,组件触发第二次重渲染,此时分组逻辑会基于更新后的favoriteContact重新计算,所以UI能显示正确结果,但因为状态更新是异步的,就出现了数据延迟的问题。

彻底解决问题的方案

方案1:用useMemo生成派生分组(推荐)

分组是基于contacts和favoriteContact计算出来的派生数据,用useMemo缓存计算结果,依赖项变化时自动重新计算,能立即拿到最新值:

const [favoriteContact, setFavoriteContact] = useState<string[]>([]);

// 从localStorage加载收藏数据
useEffect(() => {
  const favorites = window?.localStorage?.getItem("favorites")
    ? JSON.parse(localStorage.getItem("favorites")!)
    : [];
  setFavoriteContact(favorites);
}, []);

// 用useMemo生成分组
const { favoriteContactGroup, normalContactGroup } = useMemo(() => {
  const favGroup: { [key: string]: Contact[] } = {};
  const normalGroup: { [key: string]: Contact[] } = {};

  contacts.forEach(item => {
    const firstLetter = item.last_name.charAt(0).toUpperCase();
    const fullName = `${item.last_name} ${item.first_name}`;

    if (favoriteContact.includes(fullName)) {
      if (!favGroup[firstLetter]) favGroup[firstLetter] = [];
      favGroup[firstLetter].push(item);
    } else {
      if (!normalGroup[firstLetter]) normalGroup[firstLetter] = [];
      normalGroup[firstLetter].push(item);
    }
  });

  return { favoriteContactGroup: favGroup, normalContactGroup: normalGroup };
}, [contacts, favoriteContact]);

方案2:把分组作为状态管理

如果需要对分组做更多可变操作,可以将其设为状态,在依赖项变化时更新:

const [favoriteContact, setFavoriteContact] = useState<string[]>([]);
const [favoriteContactGroup, setFavoriteContactGroup] = useState<{ [key: string]: Contact[] }>({});
const [normalContactGroup, setNormalContactGroup] = useState<{ [key: string]: Contact[] }>({});

useEffect(() => {
  const favorites = window?.localStorage?.getItem("favorites")
    ? JSON.parse(localStorage.getItem("favorites")!)
    : [];
  setFavoriteContact(favorites);
}, []);

// 当依赖变化时更新分组状态
useEffect(() => {
  const favGroup: { [key: string]: Contact[] } = {};
  const normalGroup: { [key: string]: Contact[] } = {};

  contacts.forEach(item => {
    const firstLetter = item.last_name.charAt(0).toUpperCase();
    const fullName = `${item.last_name} ${item.first_name}`;

    if (favoriteContact.includes(fullName)) {
      if (!favGroup[firstLetter]) favGroup[firstLetter] = [];
      favGroup[firstLetter].push(item);
    } else {
      if (!normalGroup[firstLetter]) normalGroup[firstLetter] = [];
      normalGroup[firstLetter].push(item);
    }
  });

  setFavoriteContactGroup(favGroup);
  setNormalContactGroup(normalGroup);
}, [contacts, favoriteContact]);

两种方案都能避免数据延迟,同时确保组件能正确感知分组变化并触发重渲染。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 13:17:07