React useState变量未调用setState却在计算时被意外修改
React状态变量意外被修改的原因及修复方案
问题描述
在React函数组件中使用useState存储数据,需要将数据转换单位后传入子组件,但每次组件重渲染时,状态变量priceData中的y值都会被自动除以1000——即便没有调用setPriceData,也没有直接修改priceData本身。
最小可复现代码
import React, { useEffect, useState } from "react" import MyComponent from "MyComponent" const PriceGraph = () => { const [priceData, setPriceData] = useState<Array<{ x: Date, y: number }>>([]) useEffect(() => { const data = [ { x: new Date("2023-04-04T13:00:00"), y: 108.71 }, { x: new Date("2023-04-04T14:00:00"), y: 112.73 } ] setPriceData(data) }, []) return ( <div> <MyComponent data={getChartData(priceData)} /> <span> {priceData[0]?.y} </span> </div> ) } function getChartData(data: Array<{ x: Date, y: number }>) { return { datasets: [ { data: mwhToKwh(data), }, ], } } function mwhToKwh(data: Array<{ x: Date, y: number }>) { return data.map(point => { point.y /= 1000 return point }) } export default PriceGraph
问题原因
核心问题是直接修改了状态中引用类型的内部属性:
priceData是存储在useState中的数组,属于引用类型,数组里的每个point对象都是原数据的引用。- 在
mwhToKwh函数中,point.y /= 1000是直接修改了原对象的y属性,而非创建新对象。这会直接改变priceData状态中存储的对象值,因为它们指向同一个内存地址。 - 每次组件重渲染时,
getChartData都会被调用,进而执行mwhToKwh,导致原对象的y值被反复除以1000。
React要求状态遵循“不可变”原则——不是完全不能修改,而是不能直接修改状态的引用或其内部属性,必须通过setState更新引用。但这里的修改绕过了setState,直接改变了状态的实际内容,所以每次重渲染都会读取到修改后的值。
修复方案
避免直接修改原状态对象,而是在转换时创建新的对象:
修改mwhToKwh函数,使用对象扩展运算符返回新对象:
function mwhToKwh(data: Array<{ x: Date, y: number }>) { return data.map(point => { // 复制原对象的所有属性,同时替换y值,不修改原对象 return { ...point, y: point.y / 1000 } }) }
也可以使用Object.assign实现同样的效果:
function mwhToKwh(data: Array<{ x: Date, y: number }>) { return data.map(point => Object.assign({}, point, { y: point.y / 1000 })) }
这样每次单位转换都是基于原数据生成新的对象数组,不会影响priceData状态中的原始数据,span里的数值就能保持初始值。
额外优化
如果getChartData的计算逻辑较复杂,或者组件重渲染频繁,可以用useMemo缓存计算结果,避免重复计算:
import React, { useEffect, useState, useMemo } from "react" const PriceGraph = () => { // ... 其他代码不变 // 仅当priceData变化时才重新计算chartData const chartData = useMemo(() => getChartData(priceData), [priceData]) return ( <div> <MyComponent data={chartData} /> <span> {priceData[0]?.y} </span> </div> ) }
内容的提问来源于stack exchange,提问作者Axel Köhler
相关产品推荐
相关产品推荐

