React组件状态中WebSocket返回玩家数组顺序异常排查
首先咱们来拆解这个诡异的现象:WebSocket传过来的数组顺序明明是对的,console.log输出也没问题,但一存入React状态就自动按place排序了——甚至你在setState的回调里看到原game对象的数组也变了,这核心问题出在JavaScript的引用类型特性和原地数组操作上。
具体原因
引用类型的“共享内存”问题
你传给setState的game是一个对象,而对象(包括数组)在JS里是引用类型——也就是说,this.state.game和你外部的game变量指向的是同一块内存地址。如果之后有代码修改了这个game对象的players数组,状态里的数组也会跟着同步变化。原地排序操作修改了原数组
你的代码里肯定存在某个逻辑(比如WebSocket回调的后续代码、某个生命周期函数、或者其他处理函数),对game.players执行了原地排序(比如Array.sort()方法)。sort()是会直接修改原数组的,不是返回新数组。
你在setState前的console.log是在排序操作之前执行的,所以看到的是正确顺序;但setState是异步的,等回调执行的时候,排序操作已经完成了,不管是原game对象还是状态里的数组,都已经被修改成排序后的样子了——这就是为什么你看到callback输出的是1,1。
验证与解决方法
1. 先验证问题根源
你可以在setState前先拷贝一份game对象,再把拷贝后的对象存入状态:
// 做一个浅拷贝,如果players里的对象不需要深层修改,这样就足够 const gameCopy = { ...game, players: [...game.players] }; console.log(gameCopy.players[0].place); // 这里应该还是原来的4 this.setState({ game: gameCopy }, () => { console.log("callback", game.players[0].place, this.state.game.players[0].place); });
如果此时this.state.game.players[0].place还是4,而原game.players[0].place变成了1,那就实锤了:是原game对象在后续被修改了。
2. 彻底解决的方案
- 始终传递拷贝后的对象到状态:避免原对象的修改污染状态。如果需要深拷贝(比如
players里的对象还有嵌套结构),可以用JSON.parse(JSON.stringify(game)),或者使用Lodash的cloneDeep方法。 - 禁止原地修改原数组:如果业务需要排序后的数组,应该对拷贝后的数组进行操作,比如:
// 不要直接修改原数组:game.players.sort(...) // 而是创建新数组 const sortedPlayers = [...game.players].sort((a, b) => a.place - b.place); // 用新数组创建新的game对象 const sortedGame = { ...game, players: sortedPlayers };
内容的提问来源于stack exchange,提问作者Pacxiu

