#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 останется тем же. Однако в Вашем случае надо изменить архитектуру, как указано в первом варианте.
Комментариев нет:
Отправить комментарий