Redux архитектура для повторного использования одного и того же компонента - PullRequest
0 голосов
/ 26 марта 2019

TLDR:

Приложение имеет N количество <Counter> компонентов, и каждый хочет отразить каждое состояние счетчиков в магазине Redux приложения.


Некоторый контекст:

Ради этого контекста каждый счетчик представляет время, которое игрок потратил, чтобы сыграть свой ход в игре с динамическим числом игроков.

Кроме того, при смене хода игрока требуется приостановить все таймеры других игроков, а при перезапуске игры все таймеры должны быть перезапущены в исходное состояние (0ms).


(Я использую шаблон ducks для моего redux кода)

timer.reducer.js

let timerIdx = 0;

// timer "constructor"
function timer(){
    const TICK    = `game/timer${timerIdx}/TICK`
    const RESTART = `game/timer${timerIdx}/RESTART`

    let value = 0;

    // reducer
    const reducer = (state = 0, action = {}) => {
        switch( action.type ){
            case TICK :
                value = action.value; // update local state
                return action.value

            case RESTART :
                return 0
        }
        return state
    }

    const actionCreators = {
        start(dispatch) {
            const startTime = performance.now(),
                  lastValue = this.value;

            function tick(){
                dispatch({ 
                    type   : TICK, 
                    player : +player, 
                    value  : lastValue + performance.now() - startTime 
                })
            }

            tick();
            this.interval = setInterval(tick, 100);

            return this.interval;
        },
        stop(){
            clearInterval(this.interval);
            this.interval = null;
        },
        restart() {
            return { type:RESTART }
        }
    }

    timerIdx++;

    return {
        reducer,
        ...actionCreators
    }
}

export default timer;

index.js (основной редуктор)

import board from './board.reducer';
import timer from './timer.reducer';

export const timer1 = timer();
export const timer2 = timer();

const { createStore, combineReducers, applyMiddleware} = Redux;

export const rootReducer = combineReducers({
    board,
    timer1 : timer1.reducer,
    timer2 : timer2.reducer
});

export const store = createStore(
    rootReducer
)

Изначально я обернул редуктор timer (см. выше) в функцию, которая возвращает «экземпляр» редуктора таймера (не настоящий экземпляр) и инкапсулировалвся «утиная» вещь в своем собственном контексте.Использование этого мышления показано в приведенном выше файле index.js (timer1 & timer2).

Это не очень надежно, так как требует, чтобы я заранее знал, сколько счетчиков нужно создатьи жестко закодировать их в главный редуктор ..

Как должен быть разработан такой сценарий в архитектуре React-Redux?

Ответы [ 3 ]

0 голосов
/ 26 марта 2019

Это несколько похоже на проблему приложения todo в примерах редуксов. (https://github.com/reduxjs/redux/tree/master/examples/todos). Я бы посоветовал сохранить один редуктор, отвечающий за таймеры и ход активного игрока, а не по одному на каждого игрока, поскольку функция редуктора будет одинаковой для всех из них. Когда игрок отправляет действие, сообщающее об обновлении своего счетчика, ему также необходимо указать в действии, что такое идентификатор этого игрока, и вы можете проверить, соответствует ли активный игрок идентификатору игрока, отправляющего действие, затем обновить этот идентификатор в состоянии. иначе проигнорируй это. Ваше состояние будет примерно таким:

{
  count: {
    '0': 1,
    '1': 0,
    '2': 3
  },
  activePlayer: 2,
  amountOfPlayers: 3,
}

(или используйте массив-индекс вместо значения ключа, если хотите) И ваши действия будут выглядеть примерно так:

{
  type: 'TICK',
  playerId: id
}

А твой редуктор:

const myReducer = (state = {}, action = {}) => {
    switch( action.type ){
        case TICK :
            if(action.playerId !== state.activePlayer) {
              return state; // ignore tick actions coming from players that are not the active one
            }

            return Object.assign({},
                state,
                count: {
                  [action.playerId]: state.count[action.playerId]+1
                }
              }
            }
        case RESTART :
            return {
              ...state,
              {
                count: Object.assign({},
                  state.count,
                  {[action.playerId]: 0})
              }
            }
        }
        case ADD_PLAYER:
          return Object.assign({},
            state,
            {amountOfPlayers: state.amountOfPlayers+1},
            {
              count: Object.assign({},
               state.count,
               {[state.amountOfPlayers]: 0}
              })
            }
          );
    return state;
}

И, наконец, если вам нужно сопоставить свое состояние с реквизитом, просто возьмите с подсчета, используя идентификатор этого игрока. Возможно, лучше написать редуктор, но я надеюсь, что вы поняли идею.

0 голосов
/ 26 марта 2019

Вам понадобится ключ с редуктором, например:

const createKeyedReducer = ({
  targetReducer,
}) => {
  const ALL = 'KEYED_ALL'
  const RESET = 'KEYED_RESET'
  const ONE = 'KEYED_ONE'
  const keyedReducer = (state = {}, action) => {
    switch (action.type) {
      case RESET:
        // might wanna use lodash.mapValues here
        return Object.keys(state).reduce((prev, key) => {
          prev[key] = targetReducer(undefined, { type: '@@redux/init'})
          return prev
        }, {})
      case ALL:
        return Object.keys(state).reduce((prev, key) => {
          prev[key] = targetReducer(state[key], action.action)
          return prev
        }, {})
      case ONE:
        return {
          ...state,
          // unpack action.action
          [action.routingKey]: targetReducer(state[action.routingKey], action.action)
        }
      default:
        return state
    }
  }

  return {
    reducer: keyedReducer,
    keyedAll: action => ({ type: ALL, action }),
    keyedReset: () => ({ type: RESET }),
    keyedOne: (routingKey, action) => ({ type: ONE, routingKey, action }),
  }
}

Ваш таймер-редуктор будет оставаться изолированным.

0 голосов
/ 26 марта 2019

Себастьян Лорбер поднял этот вопрос вскоре после того, как вышел Redux. В конце концов он создал репозиторий slorber / scalable-frontend-with-elm-or-redux , в котором собраны пользовательские примеры решений такого рода проблем, написанные с использованием различных реализаций.

На этот вопрос нет однозначного ответа, но я бы посоветовал вам посмотреть на этот репозиторий для идей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...