React useReducer删待办触发undefined的map读取报错
问题描述
在React项目中使用useReducer hook实现Todo待办列表的新增、删除功能时,测试删除逻辑出现异常。
- 报错信息:
TypeError: Cannot read properties of undefined (reading 'map') - 复现规律:
- 将reducer中
deleteItem类型action对应的返回逻辑修改为和addItem分支一致的结构时,功能可正常运行 - 如果修改为其他逻辑(例如仅执行
console.log、或直接返回数组过滤结果),就会触发上述报错
- 将reducer中
相关代码
App组件代码
import React, { useState, useReducer } from "react"; import { FaBeer } from "react-icons/fa"; import ToDoItem from "./components/ToDoItem"; import "./App.css"; function App() { const [inputText, setInputText] = useState(""); const [state, dispatch] = useReducer(reducer, [], init); function init(initArray) { return { todos: initArray }; } function reducer(state, action) { switch (action.type) { case "addItem": return { todos: [...state.todos, action.payload.inputText] }; case "deleteItem": return state.todos.filter((item, index) => { return index !== action.payload.id; }); // return { todos: [...state.todos, action.payload.id] }; default: return state.todos; } } function handleChange(event) { const newValue = event.target.value; setInputText(newValue); } function addItem(e) { e.preventDefault(); dispatch({ type: "addItem", payload: { inputText: inputText } }); setInputText(""); } return ( <div className="container"> <div className="heading"> <h1>To-Do List</h1> </div> <div className="form"> <form onSubmit={addItem}> <input onChange={handleChange} type="text" value={inputText} /> <button type="submit"> <span>Add</span> </button> </form> </div> <div> <ul> {state.todos.map((eachItem, index) => ( <ToDoItem key={index} id={index} text={eachItem} dispatch={dispatch} /> ))} </ul> </div> </div> ); } export default App;
ToDoItem组件代码
import React from "react"; import audio from './clickSound.wav'; import CheckBox from "./CheckBox"; import Delete from "./Delete"; function ToDoItem(props) { const [clicked, setClicked] = React.useState(false); function cross() { setClicked((prevValue) => { return !prevValue; }); } function playSound(){ const sound = new Audio(audio) sound.play() } return ( <div> <CheckBox /> <label> <input type="checkbox" className="normalCheckbox" onClick={()=>{cross();playSound();}} /> <li style={{ textDecoration: clicked ? "line-through" : "none" }}> {props.text} </li> </label> <Delete onClick={() => props.dispatch({ type: "deleteItem", payload: { id: props.id } }) } /> </div> ); } export default ToDoItem;
Delete组件代码
import React from 'react'; import { AiFillDelete} from "react-icons/ai"; const Delete = props => { return <button onClick={props.onClick}type="button"> <AiFillDelete/> </button> }; export default Delete;
问题根源
报错的核心原因是reducer返回的状态结构和初始状态不一致:
- 初始化时通过
init函数返回的state结构为{ todos: 待办数组 },addItem分支也遵守了这个结构,返回带todos属性的对象 deleteItem分支直接返回了filter后的纯数组,导致state从对象变成数组类型,后续渲染读取state.todos时,数组本身没有todos属性,拿到的值是undefined,调用map方法就会抛出类型错误- 如果
deleteItem分支只写console.log不写返回值,函数默认返回undefined,state会被更新为undefined,读取state.todos同样会报错 - 之前测试时把
deleteItem分支改成和addItem一致的返回结构(即返回带todos属性的对象)时不报错,本质是状态结构保持了统一,只是删除逻辑本身不正确而已。另外default分支直接返回state.todos也存在同样的结构错误问题。
修复方案
修改reducer逻辑,确保所有分支始终返回结构统一的状态对象:
function reducer(state, action) { switch (action.type) { case "addItem": return { todos: [...state.todos, action.payload.inputText] }; case "deleteItem": // 包裹在对象中返回,保持state结构一致 return { todos: state.todos.filter((item, index) => index !== action.payload.id) }; default: // 默认返回完整原状态,不要返回state.todos return state; } }
注意:
useReducer的reducer是纯函数,必须始终返回与初始状态结构完全一致的新状态,禁止随意修改状态的数据类型,也不能省略返回值导致返回undefined。
内容的提问来源于stack exchange,提问作者s c
相关产品推荐
相关产品推荐

