ReactJS数组元素跨数组转移失效及重复key问题排查
我有两个存储电影分类的数组selectedGenres和genres,想用Material UI的<Chip />组件通过.map渲染分类。预期效果是:点击未选中的分类(来自genres),就把它移到selectedGenres并从genres里移除;点击已选中的分类则反向操作。但现在onClick和onDelete完全没效果,还弹出「两个不同对象使用相同key」的错误。数据是从TMDB API获取的,相关代码如下:
Genres.jsx代码
import Chip from "@mui/material/Chip"; const Genres = ({ selectedGenres, setSelectedGenres, genres, setGenres, type, setPage, }) => { // 添加与移除函数 const handleAdd = (genre) => { setSelectedGenres([...selectedGenres, genre]); setGenres(genres.filter((g) => g.id !== genre.id)); }; const handleRemove = (genre) => { setGenres([...genres, genre]); setSelectedGenres( selectedGenres.filter((selected) => selected.id !== genre.id) ); }; const fetchGenres = async () => { const apiKey = MY_API_KEY; const { data } = await axios.get( `https://api.themoviedb.org/3/genre/${type}/list?api_key=${apiKey}&language=en-US` ); setGenres(data.genres); }; useEffect(() => { fetchGenres(); }); return ( // 两类Chip的React渲染函数 {selectedGenres.map((genre) => ( <Chip sx={{ margin: "5px" }} label={genre.name} key={genre.id} color="primary" clickable onDelete={() => handleRemove(genre)} /> ))} {genres.map((genre) => ( <Chip sx={{ margin: "5px" }} label={genre.name} key={genre.id} clickable onClick={() => handleAdd(genre)} /> ))} ) }
Movies.jsx代码
import Genres from "./Genres.jsx"; const Movies = () => { const [genres, setGenres] = useState([]); const [selectedGenres, setSelectedGenres] = useState([]); return ( <Genres type="movie" selectedGenres={selectedGenres} setSelectedGenres={setSelectedGenres} genres={genres} setGenres={setGenres} setPage={setPage} /> ) }
问题根源
重复Key错误原因:
你的useEffect没有设置依赖项,组件每次渲染都会重新调用fetchGenres,把TMDB返回的全部分类重新塞回genres数组。这就导致已经被移到selectedGenres的分类,又被重新加回genres,同一个分类ID同时出现在两个数组里,渲染时触发重复key警告。功能失效原因:
同样是useEffect无依赖的问题——每次你点击Chip修改数组状态,组件都会重渲染,进而触发fetchGenres重置genres数组,把你刚修改的状态直接覆盖,所以点击操作看起来完全没效果。
修复方案
1. 给useEffect添加正确依赖项
只在组件首次挂载或type参数变化时拉取分类,避免重复请求重置数据:
import { useEffect } from 'react'; // 别忘了导入useEffect // ...其他代码 useEffect(() => { fetchGenres(); }, [type]); // 仅当type变化时重新执行
2. 优化状态更新逻辑(可选)
把状态更新改成使用回调函数,避免闭包导致的状态快照问题:
const handleAdd = (genre) => { setSelectedGenres(prevState => [...prevState, genre]); setGenres(prevState => prevState.filter(g => g.id !== genre.id)); }; const handleRemove = (genre) => { setGenres(prevState => [...prevState, genre]); setSelectedGenres(prevState => prevState.filter(selected => selected.id !== genre.id)); };
3. 补充缺失的导入
你的代码里用了axios但没导入,需要补上:
import axios from 'axios';
总结
重复key错误和功能失效不是key的用法问题(用genre.id作为key是正确的),核心原因是useEffect没有依赖项导致的重复请求重置数据。修复useEffect的依赖后,两个问题都会解决。
内容的提问来源于stack exchange,提问作者Visal Yogaraj

