#javascript #reactjs #redux
Пишу игру тест.Угадай по картинке.
Надо угадать кто в картинке.Есть 4 варианта.Выбираешь одну если правильно следующий
вопрос иначе показывает что вы не угадали и game over.
Хочу добавить таймер.Чтобы когда игра начиналась начался обратный отсчет с 10 секунд
если за это время игрок не ответил то game over.
С этим у меня большие проблемы ибо не знаю как правильно выполнять асинхронные код
в Redux.Помогите пожалуйста.
Покажу свой reducer чтобы обяснить если надо будет кому то проста скажите и я добавлю
весь код(он слишком большой и думаю не нужен для того чтобы понять эту часть поэтому
не добавил) проста думаю что взглянув на reducer можно все понять вся логика там.
let startNumb = 0;
let finishNumb = 4;
let countdown = 10;
const startData = data; //data это другой js файл типо REST
shuffle(startData); // перемешиваю дату
function shuffle(arr) {
// Функция которая перемещает массив ничего особенного
}
const initialState = { //Начальный стейт
counter:0, //таймер
nameAndImage: startData.slice(startNumb, finishNumb), //каждый раз игра должен
взять из перемешанного массива 0-вой и 4-ый елемент и показать
trueOrFalse:null, //Это свойства обьекта отвечает за то что если игра закончилось
то показывает компонент YouWin иначе компонент False
started:false, //если игра началось то это свойство true
timer:countdown, //наш таймер
}
const rootReducer = (state = initialState, action) => {
switch (action.type) { //Если action.type true
case actionTypes.WATCHTIMER:
return {
...state, //Возвращаем стейт
timer:--countdown, // Yменьшаем countdown на 1
}
case actionTypes.GAMEISSTARTED: //Если игра началось
return {
...state, //Возвращаем стейт
started: true, //меняем started false на true
}
// ЕСЛИ КЛИКНУТО НА ОДИН ИЗ ВАРИАНТОВ ------------
case actionTypes.TRUEVARIANT:
const source = action.imageSrc; //Берем из action-а src картинки
const imageName = source.slice(35, source.length - 13); //обрезаем получив
только имя картинки
// ------------ Если ответ правильный ------------
if (action.payload === imageName) { //То есть ответ который приходит
с payload равен имени картинки которое мы обрезали
// ------------ И если вопросы не закончились ------------
if (finishNumb < startData.length) {
return {
...state, //Возвращаем стейт
nameAndImage: startData.slice(startNumb += 4, finishNumb
+= 4), //переходим к следующей четверке вопросов
}
} else {
// ------------ Если вопросы закончились возвращаем следуищее------------
return {
nameAndImage: null,
started: true,
trueOrFalse: YouWin,//компонент YouWin которое показывает
что вы победили
}
}
} else {
// ------------ ЕСЛИ ОТВЕТ НЕПРАВИЛЬНЫЙ ------------
return {
nameAndImage: null,
started: true,
trueOrFalse: False,//компонент False которое показывает что вы
проиграли
}
}
default:
console.log("default works"); //дефолт
}
return state; //возвращаем стейт
}
Где работает таймер?
Я реализовал в action.creator-е watchTimer (Работает redux-thunk)
export const watchTimer = _ => {
return dispatch => {
setInterval(() => {
dispatch({
type: WATCHTIMER,
})
}, 1000);
}
}
Но такой подход каждую секунду возвращает новые вопросы то есть как вы поняли стейт
меняетсья каждую секунду если убрать отсуда стейт
case actionTypes.WATCHTIMER:
return {
...state,
timer:--countdown,
}
То сразу будет ошибка.
Мне нужно чтобы работал таймер не меняя стейт как этого достичь в Redux?
Вот код и целый проект на codesandbox
Ответы
Ответ 1
Я посмотрел код проекта. Однозначно можно сказать, что вы нарушили, пожалуй, ключевой принцип React, а именно то, что видимое состояние интерфейса является отражением внутреннего состояния приложения, и каждое изменение интерфейса - это реакция на изменение внутреннего состояния. Отсюда собственно и название библиотеки - React. Источником данных для компонент может служить либо глобальное состояние приложения (тот самый state, который формируетcя через redux) - в этом случае данные приходят как props. Либо же это локальный state компонента (this.state, который обновляется через setState() метод). У Вас же проблема в том, что источником данных служит ни глобальный state, ни локальный, а генератор случайных чисел, который, разумеется, всякий раз возвращает новое число, а так как он у Вас используется при каждом обновлении компонента, которое в свою очередь вызывается обновлением глобального состояния приложения, то всякое такое изменение, в частности уменьшение счетчика времени, приводит к тому, что меняется и весь вопрос случайным образом. Есть два варианта решения проблемы. 1. Правильный вариант. Вам надо изменить архитектуру приложения. Формирование вопроса, при котором используется случайное число, должно происходить не в компоненте, а в reducer'e. То есть когда игра начинается, то Вы по этому action должны внутри reducer'a подготовить картинку и варианты ответа (и можно еще и правильный ответ - сейчас он получается каким-то нетривиальным образом), а потом эти данные через mapStateToProps передать компоненту. В самом компоненте не должно быть обращений к генератору случайных чисел при каждом обновлении. Сделав так, Вы добьетесь того, что интерфейс будет отражением состояния приложения. И поэтому, когда Вы лишь уменьшите счетчик времени, то так как ничего больше не поменяется в state, то ничего не поменяется и в интерфейсе (кроме самого счетчика разумеется). После того, как выбран ответ на вопрос, Вы совершаете все манипуляции и готовите следующий вопрос (используя случайное число) - картинку и варианты ответа, и так далее. То ВСЕ данные описывающие внешний вид приложения, должны хранится в state'e. И этот state меняется только тогда, когда был послан тот или иной action (начало игры, ответ на вопрос и пр.). Хранить ссылку на картинку и список вариантов ответа отдельно от общего реестра не обязательно - если есть возможность получить их из всего массива данных по индексу или ключу, то надо просто генерировать набор индексов/ключей для текущего вопроса, чтобы избегать избыточности (теоретически можно даже хранить сами случайные числа и на их основе получать данные - та же логика, что и сейчас, но только полученное случайное число должно не должно меняться при каждом обновлении состояния/компонента). 2. Неправильный вариант. Я очень не рекомендую это использовать в Вашем случае, но это полезно знать для общего развития. React позволяет отменить обновление элемента. Для этого можно использовать shouldComponentUpdate() метод или же унаследовать компонент от PureComponent, который этот shouldComponentUpdate() уже и содержит. Это оптимизация, которая используется, чтобы избежать ненужных обновлений, когда ничего не поменялось. В Вашем случае меняется счетчик времени, поэтому воспользоваться shouldComponentUpdate() нельзя. Однако функция connect() из react-redux содержит внутри себя подобную оптимизацию - если свойства полученные из mapStateToProps остались те же, то компонент не будет обновляться. Если Вы сделаете элемент со счетчиком времени отдельным контейнером (завернете в отдельный connect()), и удалите значение time из mapStateToProps компонента Main, то так как при изменении этого значения не меняется ничего более, то обновляться компонент Main тоже не будет (а счетчик будет), поэтому и вопрос не будет меняться, так Main останется тем же. Однако в Вашем случае надо изменить архитектуру, как указано в первом варианте.
Комментариев нет:
Отправить комментарий