setState调用顺序是否会导致Undefined is not an object (evaluating '_ref5.x')报错?
报错根本原因
你遇到的问题核心是React 17及更早版本中,await 这类异步回调里的 setState 不会走批量更新逻辑,每调用一次 setState 就会立刻触发一次组件重渲染,再加上你代码里的状态依赖逻辑,就出现了顺序影响报错的情况。
你看到的_ref5是代码编译打包后的变量别名,对应的就是你代码里某个要访问.x属性的对象,报错就是因为这个对象在中间渲染态是undefined:
- 原来的执行顺序(先
setArr,后setTerminal):- 调用
setArr(data)后立刻触发第一次重渲染,此时对应日期的价格数组已经更新为新的data,但terminal还是旧值。如果你的JSX渲染逻辑、或者其他依赖这两个状态的useEffect里,有代码需要同时用到两个状态的最新值,就会出现数据不匹配,刚好访问到了undefined对象的.x属性,直接抛错。 - 第一次重渲染完成后才会执行
setTerminal,触发第二次重渲染,此时两个状态才同步为有效值,但第一次的报错已经抛出来了。
- 调用
- 调整顺序后(先
setTerminal,后setArr):
两次重渲染的中间态没有出现逻辑不兼容的状态组合,所以不会触发报错。但这只是临时绕开了问题,没有解决根本的状态不同步漏洞。
代码优化建议
1. 把关联的状态更新合并为单次重渲染
不管用哪个版本的React,都建议把相关联的状态更新合并,避免出现中间态:
- 用React 18的话默认所有更新都会批量处理,不需要额外操作,不管顺序怎么写都只会触发一次重渲染;
- 用React 17及更早版本的话,可以用官方提供的批量更新方法包裹两次setState:
// React Native 里从'react-native'导入即可 import { unstable_batchedUpdates } from 'react-native'; // 调用的时候直接包裹即可 unstable_batchedUpdates(() => { setArr(data); setTerminal(data[data.length - 1].val); })
这样两次更新会合并成一次重渲染,两个状态同时更新,不管顺序怎么写都不会出现中间态不匹配的问题。
2. 删掉冗余的重复状态定义
你现在定义了5个结构完全相同的价格数组状态,还要单独维护dayToArray映射,不仅代码冗余,还有快照问题:dayToArray写在组件顶层,每次重渲染都会重新创建,里面存的状态值都是当前渲染周期的快照,永远拿不到最新值,后续很容易踩坑。
可以直接把所有日期的价格存在同一个状态里:
// 一个状态存所有天数的价格 const [prices, setPrices] = useState({ 1: [], 2: [], 3: [], 4: [], 5: [] }); const [terminal, setTerminal] = useState('0');
要用对应天数的价格直接取prices[day]就行,更新的时候也只要操作这个对象,不用维护5个单独的state和映射关系,代码简洁很多。
3. 修复疑似笔误和空值问题
你原有代码的缓存判断分支里写的是arrRef[arr.length - 1].val,看起来大概率是笔误,应该是要取arr[arr.length - 1].val?如果arrRef是你单独定义的ref,要注意每次价格数组更新的时候同步更新ref的current值,不然会出现取值不准的问题。
另外建议所有取值的地方都加上可选链做兼容,比如data?.[data.length - 1]?.val,避免API返回空数组或者异常值的时候直接抛错。
内容的提问来源于stack exchange,提问作者Craig

