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

点击@headlessui/react排序菜单后React组件无法重渲染的问题

解决点击排序菜单后组件不重渲染的问题

问题根源

  • 直接修改状态数组:组件顶部的contacts.sort(...)是原地修改useState返回的数组,违反React状态不可变原则,会导致状态更新异常,且每次组件渲染都会执行该排序,干扰预期逻辑。
  • 排序逻辑冗余混乱:在点击事件里手动排序并更新contacts,同时尝试用useEffect处理,逻辑重复易引发状态不一致。
  • 内部组件重复创建:ListContact定义在父组件内部,每次父组件渲染都会重新生成该组件,可能触发不必要的重渲染或状态问题。

修复方案

1. 移除原地修改状态的代码

直接删除这行违规代码:

contacts.sort((a, b) => a.name.localeCompare(b.name));

2. 用useMemo生成排序后的数组

保留原始contacts状态不变,根据selectedOptionName的变化,通过useMemo实时计算排序结果,确保状态不可变且自动触发重渲染:

const sortedContacts = useMemo(() => {
  return [...contacts].sort((a, b) => {
    return selectedOptionName === 'ins' 
      ? a.name.localeCompare(b.name) 
      : b.name.localeCompare(a.name);
  });
}, [contacts, selectedOptionName]);

3. 简化点击事件,仅更新排序选项

点击菜单项时只需更新selectedOptionName,排序逻辑交给useMemo处理,无需手动修改contacts:

// 升序点击逻辑
onClick={() => setSelectedOptionName('ins')}

// 降序点击逻辑
onClick={() => setSelectedOptionName('des')}

4. 将ListContact移到父组件外部

避免每次父组件渲染都重新创建子组件,提升性能:

// 移到ViewListFriend组件外部
function ListContact({ list, selectedOptionName, setSelectedOptionName }) {
  return (
    <div>
      <Menu as="div">
        <div>
          <Menu.Button>
            <p>{selectedOptionName === 'ins' ? 'Tên(A-Z)' : 'Tên (Z-A)'}</p>
          </Menu.Button>
        </div>
        <Menu.Items>
          <div>
            <Menu.Item>
              {({ active }) => (
                <button
                  onClick={() => setSelectedOptionName('ins')}
                  className={active ? 'bg-gray-100' : ''}
                >
                  Tên (A-Z)
                </button>
              )}
            </Menu.Item>
            <Menu.Item>
              {({ active }) => (
                <button
                  onClick={() => setSelectedOptionName('des')}
                  className={active ? 'bg-gray-100' : ''}
                >
                  Tên (Z-A)
                </button>
              )}
            </Menu.Item>
          </div>
        </Menu.Items>
      </Menu>

      {list.map(item => (
        // 替换为你的item渲染逻辑,示例:
        <div key={item.id} className="p-2 border-b">{item.name}</div>
      ))}
    </div>
  );
}

function ViewListFriend() {
  const [contacts, setContacts] = useState([....]); // 你的初始联系人数据
  const [selectedOptionName, setSelectedOptionName] = useState('ins');

  const sortedContacts = useMemo(() => {
    return [...contacts].sort((a, b) => {
      return selectedOptionName === 'ins' 
        ? a.name.localeCompare(b.name) 
        : b.name.localeCompare(a.name);
    });
  }, [contacts, selectedOptionName]);

  return (
    <div className="flex flex-1 flex-col">
      <ListContact 
        list={sortedContacts} 
        selectedOptionName={selectedOptionName} 
        setSelectedOptionName={setSelectedOptionName} 
      />
    </div>
  );
}
export default ViewListFriend;

核心说明

  • 状态不可变:永远不要直接修改useState返回的数组/对象,必须通过创建新数组(如[...contacts])来修改,确保React能检测到状态变化并触发重渲染。
  • useMemo缓存:只有当contacts或selectedOptionName变化时才重新计算排序结果,避免无效排序和重渲染,优化性能。
  • 组件分离:子组件移到父组件外部,避免重复创建组件带来的性能损耗和状态异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:55:09