Я делаю многопользовательскую игру на React и использую Colyseus.js в качестве многопользовательского API. Дело в том, что мне нужно отобразить всех подключенных игроков в лобби:
export default function Lobby() {
const classes = useStyles();
const gameState = useSelector(state => state.roomState);
const history = useHistory();
// Creating array to display all connected players
let playersArray = [];
if(Object.entries(gameState).length !== 0) {
playersArray = Object.values(gameState.players.toJSON());
}
// Check if game has started
useEffect(()=>{
console.log('gamestarted:', gameState.gameStarted);
if(gameState.gameStarted){
history.push('/game')
}
}, [gameState.gameStarted]);
return (
<PageFrame>
<Grid
container
direction="column"
alignItems="center"
className={classes.root}
spacing={2}
>
<Grid item xs={12}>
<Typography className={classes.gameTitle}>
{(gameState != null) ? gameState.gameName : <CircularProgress />}
</Typography>
</Grid>
<Grid item xs={12}>
Waiting for all players to join...
</Grid>
<Grid item xs={12}>
Connected Players: {playersArray.length}
</Grid>
<Grid item xs={12}>
{playersArray.map((data, index) => {
return (
<Chip
key={index}
label={data.party}
className={classes.chip}
style={{"--color": green[500]}}
/>
);
})}
</Grid>
<Grid item xs={12}>
</Grid>
</Grid>
</PageFrame>
);
}
Я использую useSelector
, чтобы получить состояние, которое я получил от Colyseus.js и отправил вот так (в другом компоненте с Lobby
как ребенок в <Route>
):
const joinRoom = (id, partyName, actions) => {
client.joinById(id, {partyName, id}).then(room => {
dispatch({type: GAME_ROOM, payload: room });
dispatch({type: RECONNECT_PARAMS, payload: {roomId: room.id, sessionId: room.sessionId} });
// Redirect to lobby
history.push('/lobby')
{...}
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: state });
});
room.onLeave((code) => {
{...}
});
}
state
с сервера имеет свойство players
с объектом, который имеет ключи с именами игроков, которые мне нужно отобразить. Мой редуктор:
const initialState = {
joinDetails:{
gameCode: '',
partyName: ''
},
sessionParams: {
roomId: "",
sessionId: "",
},
room: {},
roomState: {},
scoreChange: null,
totalChange: null,
playerState:{
showResults: false,
questions: [],
totalBudget: 5000,
totalGlobalBudget: 5000,
budget: [],
activeStep: 0,
stepCompleted: [false,false,false,false,false],
decisions: [-1,-1,-1,-1],
}
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case JOIN_FORM:
return {...state, joinDetails: action.payload};
case GAME_ROOM:
return {...state, room: action.payload};
case GAME_STATE:
return {...state, roomState: action.payload};
case RECONNECT_PARAMS:
return {...state, sessionParams: action.payload};
case CHANGE_STEP:
if(action.payload === 'add'){
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep + 1}}}
else{
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep - 1}}}
case COMPLETE_STEP:
return {...state, playerState: {...state.playerState, stepCompleted: action.payload}};
case SET_BUDGET:
return {...state, playerState: {...state.playerState, budget: action.payload}};
case SET_QUESTIONS:
return {...state, playerState: {...state.playerState, questions: action.payload}};
case SET_TOTAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalBudget: state.playerState.totalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalBudget: action.payload}};
}
case SET_GLOBAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalGlobalBudget: state.playerState.totalGlobalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalGlobalBudget: action.payload}}
}
case SET_DECISIONS:
return {...state, playerState: {...state.playerState, decisions: action.payload}};
case SET_CHANGES:
return {...state, scoreChange: action.payload.change, totalChange: action.payload.total};
case SHOW_RESULTS:
return {...state, playerState: {...state.playerState, showResults: action.payload}};
case CLEAR_STATE:
return {...state, playerState: {}};
default:
return state;
}
}
export default rootReducer;
Проблема в том, что список игроков не обновляется после присоединения новых игроков. Я могу подтвердить, что:
- Я получаю состояние отсервер с новыми игроками
- Я отправляю это состояние успешно для редукции (я вижу это через redux devtools)
- Похоже, что Lobby () просто не перерисовывается, несмотря на то, что
useSelector
работает,потому что каждый игрок видит список игроков, которые присоединились до него.
Я пытался искать видоизмененное состояние, но мне кажется, что там все хорошо. Это мой первый редукционный проект, так что, может быть, я что-то упустил. Одна подозрительная вещь, которую я заметил в redux devtools, заключается в том, что иногда (!) Последнее состояние распространяется через все предыдущие состояния в представлении devtools, например, вкладка «Diff» показывает, что все те же действия не привели к изменениям, за исключением того, что первое изменило состояние на конечную форму,например:
Фактические изменения:
- ACTION изменил некоторое свойство на
- ACTION изменил некоторое свойство на b
- ACTION изменил некоторое свойство наc
Показанные изменения (но они стали такими только после третьего раза):
- ACTION изменил какое-то свойство на a, какое-то свойство на b, а какое-то свойство на c
- ДЕЙСТВИЕ ничего не изменило
- Действие ничего не изменило
Не уверен, что это нормально или вызывает мою проблему.
Если вам нужен дополнительный код, дайте мне знать.
Спасибо.
РЕДАКТИРОВАТЬ: Изменены некоторые имена, согласно совету в комментариях, проблема по-прежнему остается.
РЕДАКТИРОВАТЬ 2: Проблема, кажется, напрямую связана с useSelector
:в настоящее время у меня есть
gameState = useSelector(state => state.roomState)
но если я выбираю все состояние, все работает, компонент перерисовывается.
gameState = useSelector(state => state)
это далеко от идеала: теперь мне нужно вызвать gameState.roomState.something
вместо gameState.something
везде. Как может быть, что useSelector
не распознает изменение определенной части состояния и только распознает изменение всего состояния?