我的状态在减速器和消费组件之间变化
Posted
技术标签:
【中文标题】我的状态在减速器和消费组件之间变化【英文标题】:My state changes between the reducer and the consuming component 【发布时间】:2021-11-17 12:27:38 【问题描述】:应用目的:这个 React 应用的目的是处理一个非常具体的飞镖游戏的得分。 2 名球员,每人必须在 20-13、Tpl、Dbls 和公牛队中达到 33 次安打。没有积分,只计算命中数。命中由玩家手动添加(不需要自动化:))。 每个目标字段都有一行目标和 2 个按钮,用于添加和删除该目标字段的匹配项。
我已经实现了用于维护状态的 useContext-design,如下所示:
export interface IMickeyMouseGameState
player1 : IPlayer | null,
player2 : IPlayer | null,
winner : IPlayer | null,
TotalRounds : number,
GameStatus: Status
CurrentRound: number
其他对象是这样设计的:
export interface IGame
player1?:IPlayer;
player2?:IPlayer;
sets: number;
gameover:boolean;
winner:IPlayer|undefined;
export interface IPlayer
id:number;
name: string;
targets: ITarget[];
wonSets: number;
hitRequirement : number
export interface ITarget
id:number,
value:string,
count:number
export interface IHit
playerid:number,
targetid:number
到目前为止一切顺利。
这是带有签名的reducer action:
export interface HitPlayerTarget
type: ActionType.HitPlayerTarget,
payload:IHit
const newTargets = (action.payload.playerid === 1 ? [...state.player1!.targets] : [...state.player2!.targets]);
const hitTarget = newTargets.find(tg =>
return tg.id === action.payload.targetid;
);
if (hitTarget)
const newTarget = ...hitTarget
newTarget.count = hitTarget.count-1;
newTargets.splice(newTargets.indexOf(hitTarget),1);
newTargets.push(newTarget);
if (action.payload.playerid === 1)
state.player1!.targets = [...newTargets];
if (action.payload.playerid === 2)
state.player2!.targets = [...newTargets];
let newState: IMickeyMouseGameState =
...state,
player1:
...state.player1!,
targets: [...state.player1!.targets]
,
player2:
...state.player2!,
targets: [...state.player2!.targets]
return newState;
在 Main 组件中我实例化了 useReducerHook:
const MickeyMouse: React.FC = () =>
const [state, dispatch] = useReducer(mickeyMousGameReducer, initialMickeyMouseGameState);
const p1Props: IUserInputProps =
color: "green",
placeholdertext: "Angiv Grøn spiller/hold",
iconSize: 24,
playerId: 1,
const p2Props: IUserInputProps =
playerId: 2,
color: "red",
placeholdertext: "Angiv Rød spiller/hold",
iconSize: 24,
return (
<MickyMouseContext.Provider value= state, dispatch >
<div className="row mt-3 mb-5">
<h1 className="text-success text-center">Mickey Mouse Game</h1>
</div>
<MickeyMouseGameSettings />
<div className="row justify-content-start">
<div className="col-5">
state.player1 ?<UserTargetList playerid=1 /> : <UserInput ...p1Props />
</div>
<div className="col-1 bg-dark text-warning rounded border border-warning">
<MickeyMouseLegend />
</div>
<div className="col-5">
state.player2 ? <UserTargetList playerid=2 /> : <UserInput ...p2Props />
</div>
</div>
</MickyMouseContext.Provider>
);
export default MickeyMouse;
现在 reducer-action 正确地从目标的计数中减去 1(重点是让每个目标计数为 0,并且新状态正确显示目标比旧状态少 1,但是当消费者(在这种情况下)一个名为 UserTargets 的 tsx 组件,它负责用圆圈或 X 渲染每个目标)目标的状态低 2,即使减速器只减去 1....
在 20 场向玩家“彼得”添加单次命中后 - 渲染(带有控制台日志)如下所示:
所以我想我的问题是:为什么减速器和消费者之间的状态会发生变化,我能做些什么来解决它?
如果需要进一步解释,请询问,如果这个问题应该简化,请告诉我...... 我通常不会在这里提问 - 我主要是找到答案。
我在github上的项目:https://github.com/martinmoesby/dart-games
【问题讨论】:
您是否将您的应用程序渲染为React.StrictMode
组件?您能否更新问题以包括 useReducer
钩子的使用位置以及您向其发送的内容? state.player1!.targets = [...newTargets];
似乎是一种状态突变。
应用处于严格模式。
我更新了问题以包括 useReducer 的实例化
【参考方案1】:
问题
我怀疑React.StrictMode
暴露了您的减速器案例中的状态突变。
StrictMode - Detecting unexpected side effects
严格模式无法自动为您检测副作用,但它 可以通过使它们更具确定性来帮助您发现它们。 这是通过有意双重调用以下函数来完成的:
类组件constructor
、render
和shouldComponentUpdate
方法 类组件静态getDerivedStateFromProps
方法 函数组件体 状态更新函数(setState
的第一个参数) 函数传递给useState
、useMemo
或useReducer
这个函数是reducer函数。
const newTargets = (action.payload.playerid === 1 // <-- new array reference OK
? [...state.player1!.targets]
: [...state.player2!.targets]);
const hitTarget = newTargets.find(tg =>
return tg.id === action.payload.targetid;
);
if (hitTarget)
const newTarget = ...hitTarget ; // <-- new object reference OK
newTarget.count = hitTarget.count - 1; // <-- new property OK
newTargets.splice(newTargets.indexOf(hitTarget), 1); // <-- inplace mutation but OK since newTargets is new array
newTargets.push(newTarget); // <-- same
if (action.payload.playerid === 1)
state.player1!.targets = [...newTargets]; // <-- state.player1!.targets mutation!
if (action.payload.playerid === 2)
state.player2!.targets = [...newTargets]; // <-- state.player2!.targets mutation!
let newState: IMickeyMouseGameState =
...state,
player1:
...state.player1!,
targets: [...state.player1!.targets] // <-- copies mutation
,
player2:
...state.player2!,
targets: [...state.player2!.targets] // <-- copies mutation
return newState;
state.player1!.targets = [...newTargets];
在更新中变异并复制到之前的state.player1
状态,当reducer 再次运行时,第二个更新再次变异并在更新中复制。
解决方案
应用不可变的更新模式。浅拷贝所有正在更新的状态。
const newTargets = (action.payload.playerid === 1
? [...state.player1!.targets]
: [...state.player2!.targets]);
const hitTarget = newTargets.find(tg => tg.id === action.payload.targetid);
if (hitTarget)
const newTarget =
...hitTarget,
count: hitTarget.count - 1,
;
newTargets.splice(newTargets.indexOf(hitTarget), 1);
newTargets.push(newTarget);
const newState: IMickeyMouseGameState = ...state ; // shallow copy
if (action.payload.playerid === 1)
newState.player1 =
...newState.player1!, // shallow copy
targets: newTargets,
;
if (action.payload.playerid === 2)
newState.player1 =
...newState.player2!, // shallow copy
targets: newTargets,
;
return newState;
【讨论】:
该死-我怎么没看到!!!所以我的问题是混合变异和非变异状态....这很完美,非常感谢!!! @MartinFrankMoesbyPetersen IDK,有时很难找到对象突变,特别是如果您不熟悉更新向量,或者您只是对代码视而不见,需要对代码进行第二次观察。我想在你看过足够多的时间之后,你会更好地直觉到哪里看。您至少包含了相关代码,所以谢谢您。 ? 干杯,祝你好运。以上是关于我的状态在减速器和消费组件之间变化的主要内容,如果未能解决你的问题,请参考以下文章