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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:50:29