点击@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
相关产品推荐
相关产品推荐

