React类组件转函数组件报错:Tasks.map is not a function
类组件转函数组件报错:Tasks.map is not a function
问题背景
尝试将React Todo应用的类组件TasksList转换为函数组件后,页面加载失败,控制台抛出错误:Uncaught TypeError: Tasks.map is not a function,错误指向列表渲染时的Tasks.map代码段。
原始类组件代码
import React, { Component } from "react"; import TaskDataService from "../services/task.service"; import FadeIn from 'react-fade-in'; import { Link } from "react-router-dom"; export default class TasksList extends Component { constructor(props) { super(props); this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this); this.retrieveTasks = this.retrieveTasks.bind(this); this.refreshList = this.refreshList.bind(this); this.setActiveTask = this.setActiveTask.bind(this); this.removeAllTasks = this.removeAllTasks.bind(this); this.searchTitle = this.searchTitle.bind(this); this.state = { Tasks: [], currentTask: null, currentIndex: -1, searchTitle: "" }; } componentDidMount() { this.retrieveTasks(); } onChangeSearchTitle(e) { const searchTitle = e.target.value; this.setState({ searchTitle: searchTitle }); } retrieveTasks() { TaskDataService.getAll() .then(response => { this.setState({ Tasks: response.data }); console.log(response.data); }) .catch(e => { console.log(e); }); } refreshList() { this.retrieveTasks(); this.setState({ currentTask: null, currentIndex: -1 }); } setActiveTask(Task, index) { this.setState({ currentTask: Task, currentIndex: index }); } removeAllTasks() { TaskDataService.deleteAll() .then(response => { console.log(response.data); this.refreshList(); }) .catch(e => { console.log(e); }); } searchTitle() { this.setState({ currentTask: null, currentIndex: -1 }); TaskDataService.findByTitle(this.state.searchTitle) .then(response => { this.setState({ Tasks: response.data }); console.log(response.data); }) .catch(e => { console.log(e); }); } render() { const { searchTitle, Tasks, currentTask, currentIndex } = this.state; return ( <FadeIn> <div className="list row"> <div className="col-md-8"> <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Search by title" value={searchTitle} onChange={this.onChangeSearchTitle} /> <div className="input-group-append"> <button className="btn btn-outline-secondary" type="button" onClick={this.searchTitle} > Search </button> </div> </div> </div> <div className="col-md-6"> <h4>Tasks List</h4> <FadeIn> <ul className="list-group"> {Tasks && Tasks.map((Task, index) => ( <li className={ "list-group-item " + (index === currentIndex ? "active" : "") } onClick={() => this.setActiveTask(Task, index)} key={index} > <div className="align-left"> {Task.title} </div> <div className="align-right"> {Task.startDate.toString().split("T")[0]} </div> </li> ))} </ul> </FadeIn> <button className="m-3 btn btn-sm btn-danger" onClick={this.removeAllTasks} > Remove All </button> </div> <div className="col-md-6"> {currentTask ? ( <FadeIn> <div> <h4>Task</h4> <div> <label> <strong>Title:</strong> </label>{" "} {currentTask.title} </div> <div> <label> <strong>Description:</strong> </label>{" "} {currentTask.description} </div> <div> <label> <strong>Status:</strong> </label>{" "} {currentTask.completed ? "Completed" : "Pending"} </div> <div> <label> <strong>Due Date:</strong> </label>{" "} {currentTask.startDate.split("T")[0]} </div> <Link to={"/Tasks/" + currentTask.id} className="badge badge-warning" > Edit </Link> </div> </FadeIn> ) : ( <div> <br /> <p>Please click on a Task...</p> </div> )} </div> </div> </FadeIn> ); } }
转换后的函数组件(存在错误)
import React, { useState, useEffect } from "react"; import TaskDataService from "../services/task.service"; import FadeIn from 'react-fade-in'; import { Link } from "react-router-dom"; function TasksList() { const [Tasks, setTasks] = useState([]); const [currentTask, setCurrentTask] = useState(null); const [currentIndex, setCurrentIndex] = useState(-1); const [searchTitle, setSearchTitle] = useState(""); useEffect(() => { retrieveTasks(); }); const onChangeSearchTitle = (e) => { const searchTitle = e.target.value; setSearchTitle({ searchTitle: searchTitle }); } const retrieveTasks = () => { TaskDataService.getAll() .then(response => { setTasks({ Tasks: response.data }); console.log(response.data); }) .catch(e => { console.log(e); }); } const refreshList = () => { retrieveTasks(); setTasks(null); setCurrentIndex(-1); } const setActiveTask = (Task, index) => { setTasks( Task ); setCurrentIndex( index ); } const removeAllTasks = () => { TaskDataService.deleteAll() .then(response => { console.log(response.data); refreshList(); }) .catch(e => { console.log(e); }); } const onSearchTitle = () => { setTasks(null); setCurrentIndex(-1); TaskDataService.findByTitle(searchTitle) .then(response => { setTasks(response.data) console.log(response.data); }) .catch(e => { console.log(e); }); } return ( <FadeIn> <div className="list row"> <div className="col-md-8"> <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Search by title" value={searchTitle} onChange={onChangeSearchTitle} /> <div className="input-group-append"> <button className="btn btn-outline-secondary" type="button" onClick={onSearchTitle} > Search </button> </div> </div> </div> <div className="col-md-6"> <h4>Tasks List</h4> <FadeIn> <ul className="list-group"> {Tasks && Tasks.map((Task, index) => ( <li className={ "list-group-item " + (index === currentIndex ? "active" : "") } onClick={() => setActiveTask(Task, index)} key={index} > <div className="align-left"> {Task.title} </div> <div className="align-right"> {Task.startDate.toString().split("T")[0]} </div> </li> ))} </ul> </FadeIn> <button className="m-3 btn btn-sm btn-danger" onClick={removeAllTasks} > Remove All </button> </div> <div className="col-md-6"> {currentTask ? ( <FadeIn> <div> <h4>Task</h4> <div> <label> <strong>Title:</strong> </label>{" "} {currentTask.title} </div> <div> <label> <strong>Description:</strong> </label>{" "} {currentTask.description} </div> <div> <label> <strong>Status:</strong> </label>{" "} {currentTask.completed ? "Completed" : "Pending"} </div> <div> <label> <strong>Due Date:</strong> </label>{" "} {currentTask.startDate.split("T")[0]} </div> <Link to={"/tasks/" + currentTask.id} className="badge badge-warning" > Edit </Link> </div> </FadeIn> ) : ( <div> <br /> <p>Please click on a Task...</p> </div> )} </div> </div> </FadeIn> ); } export default TasksList;
错误原因分析
- useEffect无限循环:
useEffect未传入依赖数组,组件每次渲染都会调用retrieveTasks,导致状态反复更新。 - 状态更新格式错误:
setSearchTitle应该直接传入字符串值,而非对象{searchTitle: searchTitle},导致searchTitle变成对象,输入框无法正常显示。retrieveTasks中setTasks错误传入对象{Tasks: response.data},而接口返回的response.data是数组,导致Tasks变成对象而非数组,无法调用map方法。
- 错误重置Tasks状态:
refreshList和onSearchTitle中调用setTasks(null),将Tasks从数组变为null,后续渲染时Tasks && Tasks.map中Tasks是null,虽然null是真值,但null.map不存在,触发报错。 - setActiveTask逻辑错误:应该更新
currentTask状态,而非用setTasks替换整个任务数组为单个任务对象,导致Tasks变成单个对象,无法map。
修正后的函数组件代码
import React, { useState, useEffect } from "react"; import TaskDataService from "../services/task.service"; import FadeIn from 'react-fade-in'; import { Link } from "react-router-dom"; function TasksList() { const [tasks, setTasks] = useState([]); const [currentTask, setCurrentTask] = useState(null); const [currentIndex, setCurrentIndex] = useState(-1); const [searchTitle, setSearchTitle] = useState(""); // 仅在组件挂载时调用一次,对应componentDidMount useEffect(() => { retrieveTasks(); }, []); const onChangeSearchTitle = (e) => { setSearchTitle(e.target.value); } const retrieveTasks = () => { TaskDataService.getAll() .then(response => { setTasks(response.data); console.log(response.data); }) .catch(e => { console.log(e); }); } const refreshList = () => { retrieveTasks(); setCurrentTask(null); setCurrentIndex(-1); } const setActiveTask = (task, index) => { setCurrentTask(task); setCurrentIndex(index); } const removeAllTasks = () => { TaskDataService.deleteAll() .then(response => { console.log(response.data); refreshList(); }) .catch(e => { console.log(e); }); } const onSearchTitle = () => { setCurrentTask(null); setCurrentIndex(-1); TaskDataService.findByTitle(searchTitle) .then(response => { setTasks(response.data); console.log(response.data); }) .catch(e => { console.log(e); }); } return ( <FadeIn> <div className="list row"> <div className="col-md-8"> <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Search by title" value={searchTitle} onChange={onChangeSearchTitle} /> <div className="input-group-append"> <button className="btn btn-outline-secondary" type="button" onClick={onSearchTitle} > Search </button> </div> </div> </div> <div className="col-md-6"> <h4>Tasks List</h4> <FadeIn> <ul className="list-group"> {tasks && tasks.length > 0 && tasks.map((task, index) => ( <li className={`list-group-item ${index === currentIndex ? "active" : ""}`} onClick={() => setActiveTask(task, index)} key={task.id || index} // 优先用task.id作为key,更可靠 > <div className="align-left"> {task.title} </div> <div className="align-right"> {task.startDate.toString().split("T")[0]} </div> </li> ))} </ul> </FadeIn> <button className="m-3 btn btn-sm btn-danger" onClick={removeAllTasks} > Remove All </button> </div> <div className="col-md-6"> {currentTask ? ( <FadeIn> <div> <h4>Task</h4> <div> <label> <strong>Title:</strong> </label>{" "} {currentTask.title} </div> <div> <label> <strong>Description:</strong> </label>{" "} {currentTask.description} </div> <div> <label> <strong>Status:</strong> </label>{" "} {currentTask.completed ? "Completed" : "Pending"} </div> <div> <label> <strong>Due Date:</strong> </label>{" "} {currentTask.startDate.split("T")[0]} </div> <Link to={`/tasks/${currentTask.id}`} className="badge badge-warning" > Edit </Link> </div> </FadeIn> ) : ( <div> <br /> <p>Please click on a Task...</p> </div> )} </div> </div> </FadeIn> ); } export default TasksList;
额外优化点:
- 将
Tasks变量名改为小写tasks,符合JavaScript变量命名规范 - 列表项key优先使用
task.id而非索引,避免列表更新时的性能问题和渲染错误 - 用模板字符串简化className拼接
- 增加
tasks.length > 0判断,避免空数组时的不必要渲染
内容的提问来源于stack exchange,提问作者alphatango165
相关产品推荐
相关产品推荐

