React如何单独计算JSON API返回的每个成绩数组总和
问题根源
你的计算逻辑存在4个核心错误,直接导致结果异常:
- 异步state更新时序错误:
gradePopulate循环中每次执行setAverage(listGrades)后,React的state更新不会同步生效,你紧接着调用Average.reduce()时拿到的是上一次渲染的旧state值,不是当前循环对应的学生成绩数组;而且循环过程中反复给同一个Averagestate赋值,最后只会保留最后一个学生的成绩数组,前面所有学生的成绩数据都会被覆盖。 - 数据类型错误:接口返回的
grades数组内元素是字符串格式,直接执行加法会触发字符串拼接,不是数值求和,就算拿到正确数组计算结果也会异常。 - 逻辑绑定错误:所有学生的展开按钮都绑定了同一个
gradePopulate方法,点击一次就会遍历全部25个学生执行计算,根本无法对应到你当前点击查看的单个学生;且渲染平均分的位置硬编码写死了9%,根本没有绑定计算结果。 - 搜索逻辑错误:两个先后执行的if判断会覆盖按firstName过滤的结果,且判断时使用的是旧的input state值,不是输入框的最新值,过滤结果不准。
修复方案
不需要单独维护全局的平均分state,直接在数据处理阶段针对单个学生的成绩数组做计算即可,同时修正搜索、展开交互的逻辑:
import React, { useEffect, useState } from "react"; import Card from "@material-ui/core/Card"; import CardContent from '@material-ui/core/CardContent'; import Grid from "@material-ui/core/Grid"; import { Input } from "@material-ui/core"; function StudentProfiles() { const [info, setInfo] = useState([]); const [input, setInput] = useState(''); const [filtered, setFiltered] = useState([]); // 单独记录展开成绩的学生id,不用存全量成绩 const [expandedId, setExpandedId] = useState(null); useEffect(() => { fetch("https://api.hatchways.io/assessment/students") .then(response => response.json()) .then(json => { setInfo(json.students) setFiltered(json.students) }) },[]); const filterChange = (e) => { const target = e.target.value; setInput(target); // 合并姓名判断,直接用最新输入值过滤 const keyword = target.toLowerCase().trim(); if(!keyword) { setFiltered(info); return } const result = info.filter((user) => { return user.firstName.toLowerCase().startsWith(keyword) || user.lastName.toLowerCase().startsWith(keyword); }); setFiltered(result); } // 单学生平均分计算工具方法 const calcAverage = (grades) => { const total = grades.reduce((sum, score) => sum + Number(score), 0); return total / grades.length; } // 切换单个学生的展开状态 const toggleExpand = (studentId) => { setExpandedId(prev => prev === studentId ? null : studentId) } return ( <div> <Card className="card"> <CardContent className="scrollbar scrollbar-primary mt-5 mx-auto"> <Input className="searchBar" icon="search" placeholder="Search by name" onChange={filterChange} value={input} /> {filtered.map((student) => ( <ul className = "border" key={student.id}> <Grid item xs={3} sm={6} md={12} style={{display: "flex", gap:"3.5rem", paddingBottom:"8px"}}> <img alt ="" src={student.pic} className="picture"></img> <Grid container style={{display: "inline"}} align="left" justifyContent="flex-end" alignItems="flex-start"> <Grid className="studentNames"> <span>{student.firstName + " " + student.lastName}</span> <Grid container align="right" justifyContent="flex-end" alignItems="flex-end" style={{display: "inline"}} > {/* 点击只切换当前学生的展开状态 */} <button onClick={() => toggleExpand(student.id)} className="plus"></button></Grid> </Grid> <span>{student.email}</span> <br/> <span>{student.company}</span> <br/> <span>{student.skill}</span> <br/> {/* 直接渲染计算好的当前学生平均分 */} <span>Average: {calcAverage(student.grades)}%</span> {/* 展开时显示当前学生的所有成绩 */} {expandedId === student.id && ( <div className="grade-list"> {student.grades.map((score, idx) => ( <div key={idx}>Test{idx+1}: {score}%</div> ))} </div> )} </Grid> </Grid> </ul> ))} </CardContent> </Card> </div> ) } export default StudentProfiles;
关键改动说明
- 移除了多余的
Average全局state,避免异步更新导致的取值错误,计算平均分时直接取当前学生的grades数组,转成数字后用reduce求和再算平均,结果完全和单个学生对应。 - 用
expandedId记录当前展开成绩的学生id,点击按钮只切换对应学生的展开状态,不会遍历全量学生做无效计算。 - 修复了搜索过滤逻辑,合并firstName、lastName的判断条件,直接使用输入事件的最新值做过滤,不会出现结果覆盖的问题。
- 接口返回数据后直接初始化
filtered列表,解决首次加载时列表为空的问题。
内容的提问来源于stack exchange,提问作者stackoverflowrog
相关产品推荐
相关产品推荐

