React设置key后仍报Each child需唯一key警告的问题排查
React列表渲染已设置key仍触发警告的排查与解决
问题描述
我在React组件中渲染菜单数组,已在<li>上设置key={index},但控制台仍弹出警告:
react-jsx-dev-runtime.development.js:119 Warning: Each child in a list should have a unique "key" prop.
组件代码如下:
import React, { useState } from "react"; import { Route, Link } from "react-router-dom"; import { MdOutlineSpaceDashboard, MdOutlineStorage, MdOutlineFactCheck, MdOutlineCalculate, MdStickyNote2, MdAssignmentTurnedIn, MdOutlineDynamicForm, MdOutlineArrowDropDown, } from "react-icons/md"; import { BsChevronDown, BsArrowLeftShort } from "react-icons/bs"; import Logo_Nabati from "../assets/logo-nabati.svg"; const menuItems = [ { id: 1, label: "Dashboard", icon: MdOutlineSpaceDashboard, link: "/" }, { id: 2, label: "Master Data", icon: MdOutlineStorage, iconArrow: MdOutlineArrowDropDown, link: "", subMenu: true, subMenuItems: [ { id: 1, label: "KSBT", link: "/MasterData/list/KSBT" }, { id: 2, label: "SQ01_RM", link: "/MasterData" }, { id: 3, label: "SQ01_PM", link: "/MasterData" }, { id: 4, label: "Depre", link: "/MasterData" }, { id: 5, label: "OMC", link: "/MasterData" }, { id: 6, label: "Premix", link: "/MasterData" }, { id: 7, label: "Routing", link: "/MasterData" }, { id: 8, label: "MP", link: "/MasterData" }, ], }, { id: 3, label: "Check COGM", icon: MdOutlineFactCheck, link: "/checkcogm" }, { id: 4, label: "Calculation", icon: MdOutlineCalculate, link: "/calculation", }, { id: 5, label: "Draft Calculation", icon: MdStickyNote2, link: "/draft", }, { id: 6, label: "Approved", icon: MdAssignmentTurnedIn, link: "/approval" }, { id: 7, label: "Task Activity", icon: MdOutlineDynamicForm, link: "/task" }, ]; const Sidebar = () => { const [open, setOpen] = useState(false); const [submenuOpen, setSubmenuOpen] = useState(false); return ( <div className="flex"> <div className={` bg-yellow-400 h-screen p-5 pt-8 ${ open ? "w-50 ease-out delay-150 peer-focus:left-0 duration-200" : "w-20 ease-out delay-150 peer-focus:left-0 duration-200" } duration-300 relative`} > <BsArrowLeftShort className={` bg-white text-yellow-300 text-3xl rounded-full absolute -right-3 top-9 border border-yellow-300 cursor-pointer delay-150 duration-200 ${ !open && "rotate-180" }`} onClick={() => setOpen(!open)} /> <div className={`inline-flex`}> <img src={Logo_Nabati} width={123} height={75} alt="logo Nabati" /> </div> <ul className="pt-8"> {menuItems.map( ({ icon: Icon, iconArrow: IconArrow, ...menu }, index) => ( <> <Link to={menu.link}> <li key={index} className="text-white text-sm text-justify flex items-center gap-x-4 cursor-pointer p-2 hover:bg-red-600 rounded-md mt-2" > <Icon className="text-2xl text-white group-hover:text-red-600" /> <span className={`text-base font-mendium flex-1 duration-200 ${ !open && "hidden" } `} > {menu.label} </span> {menu.subMenu && ( <BsChevronDown className={`text-base font-mendium duration-200 ${ !open && "hidden" } ${submenuOpen && "rotate-180"}`} onClick={() => { setSubmenuOpen(!submenuOpen); }} /> )} </li>{" "} </Link> {menu.subMenu && submenuOpen && open && ( <ul> {menu.subMenuItems.map((subMenuItem, index) => ( <Link to={subMenuItem.link}> <li key={index} className="text-white text-sm flex items-center gap-x-4 cursor-pointer p-1 px-12 hover:bg-red-500 rounded-md" > {subMenuItem.label} </li>{" "} </Link> ))} </ul> )} </> ) )} </ul> </div> </div> ); }; export default Sidebar;
问题原因
你把key设置在了<li>标签上,但map循环返回的最外层元素是无key的Fragment(<>)。React要求列表中每个直接子元素必须拥有唯一key,这里Fragment作为map的返回项,才是需要绑定key的层级,内层的<li>的key无法替代外层Fragment的key。
另外,用index作为key存在隐患:如果列表元素顺序发生变化(比如新增、删除、排序),index会跟着改变,可能导致React复用错误的组件实例,引发渲染异常。
解决方案
1. 给Fragment添加唯一key
将短语法<>替换为完整的<React.Fragment>并绑定key,推荐使用菜单item的id而非index:
{menuItems.map(({ icon: Icon, iconArrow: IconArrow, ...menu }) => ( <React.Fragment key={menu.id}> {/* 原有Link和li代码 */} {/* 原有子菜单代码 */} </React.Fragment> ))}
2. 优化子菜单的key
子菜单的map也建议用subMenuItem.id替代index,同时把key移到<Link>上(Link是map返回的直接子元素):
{menu.subMenuItems.map((subMenuItem) => ( <Link to={subMenuItem.link} key={subMenuItem.id}> <li className="text-white text-sm flex items-center gap-x-4 cursor-pointer p-1 px-12 hover:bg-red-500 rounded-md"> {subMenuItem.label} </li>{" "} </Link> ))}
3. 额外优化:子菜单展开状态独立
当前代码用一个全局的submenuOpen控制所有子菜单的展开/收起,会导致点击任意子菜单箭头时所有子菜单同步切换。建议改成用对象存储每个子菜单的展开状态:
const [submenuOpen, setSubmenuOpen] = useState({}); // 切换子菜单状态的函数 const toggleSubmenu = (menuId) => { setSubmenuOpen(prev => ({ ...prev, [menuId]: !prev[menuId] })); }; // 渲染时判断当前菜单是否展开 {menu.subMenu && ( <BsChevronDown className={`text-base font-mendium duration-200 ${!open && "hidden"} ${submenuOpen[menu.id] && "rotate-180"}`} onClick={() => toggleSubmenu(menu.id)} /> )} // 子菜单渲染条件 {menu.subMenu && submenuOpen[menu.id] && open && ( {/* 子菜单代码 */} )}
内容的提问来源于stack exchange,提问作者JS24
相关产品推荐
相关产品推荐

