繁体   English   中英

Redux架构,用于相同组件的可重用性

[英]Redux architecture for same-component reusability

TLDR:

一个应用程序有N<Counter>组件,一个希望在应用程序的Redux商店中反映每个计数器的状态。


一些背景:

为了这个上下文,每个计数器代表玩家在具有动态数量的玩家的游戏中玩转的时间。

当改变玩家的回合时,还需要暂停所有其他玩家的计时器,并且当重新启动游戏时,所有计时器应该重新启动到其初始状态( 0ms )。


(我在我的redux代码中使用ducks设计模式)

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 reducer(上面)包装在一个函数中,该函数返回一个timer reducer“instance”(不是一个真实的实例),并将整个“duck”的东西封装在它自己的上下文中。 上述index.js文件( timer1timer2 )中显示了这种思路的用法。

这不是很强大,因为它要求我事先知道应该创建多少个计数器,并将它们硬编码到主减速器中。

如何在React-Redux架构中设计这样的场景?

在Redux出来后不久,Sebastien Lorber提出了这个问题。 他最终创建了一个repo, slorber / scalable-frontend-with-elm-or-redux ,它收集用户提交的解决这些问题的解决方案,这些问题是用不同的实现编写的。

这个问题没有一个单一的答案,但我鼓励你看看那个回购的想法。

这有点类似于redux示例中的“todo app”问题。 https://github.com/reduxjs/redux/tree/master/examples/todos )。 我建议你让一个减速器负责定时器和主动玩家转而不是每个玩家一个,因为减速器功能对于所有这些都是相同的。 当玩家发出告知更新其计数器的动作时,它还需要在动作中指定该玩家ID是什么,并且您可以检查活动玩家是否匹配调度该动作的玩家的id然后更新该状态中的该ID ,否则忽略它。 你的州将是这样的:

{
  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;
}

最后,如果您需要将状态映射到道具,只需使用该播放器的ID从计数中获取。 也许减速机可以写得更好,但我希望你能得到这个想法。

你需要一种键控减速器,如下所示:

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 }),
  }
}

您的定时器减速器将保持隔离状态。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM