Страницы

Поиск по вопросам

среда, 25 декабря 2019 г.

Как правильно выполнять асинхронные действие в Redux?

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

Комментариев нет:

Отправить комментарий