簡體   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