Wygodne Reducery

Sposób na pisanie czytelnych Reducerów w Reduxie

Dokumentacja Reduxa, pokazująca jego podstawy, podaje jeden przepis na pisanie reducerów. Nie jest on skomplikowany, gdyż korzysta z każdemu znanej konstrukcji switch() case.

Wygląda on tak:

function todoApp(state, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      const newState = {...state, visibilityFilter: action.filter};
      return newState;
    case SET_PRIVS:
        return setNewPrivs(state);
    default:
      return state
  }
}

Gdy zaczniemy dorzucać kolejne akcje, składnia stanie się szybko nieczytelna.

A co, gdyby reducer wyglądał tak:

function setNewPrivs(state, action) {
  const newState = {...state, newPrivs: action.privs};
  return newState;
}
// ...
// obiekt z akcjami
{
  [SET_VISIBILITY_FILTER](state, action) {
    const newState = {...state, visibilityFilter: action.filter};
    return newState;
  },
  [SET_PRIVS] : setNewPrivs,
}

Składnia jest o wiele czytelniejsza, nie ma problemu z zasięgiem stałych, nie trzeba pamiętać o przypadku default: return state;.

Spróbujmy więc zaimplementować funkcję, która będzie przyjmowała taki obiekt z akcjami, i tworzyła na jego podstawie działający reducer. Nazwiemy ją createReducer:

export const createReducer = (initialState, handlers) => {
  return function reducer(state = initialState, action) {
    if (handlers.hasOwnProperty(action.type)) {
      return handlers[action.type](state, action);
    } else {
      return state;
    }
  }
};

Parametr initialState to początkowy stan danego reducera, natomiast handlers to nasz obiekt z akcjami (ten przykładowy, pokazany wyżej).

Funkcja createReducer zwraca inną funkcję, o nazwie reducer. Jednak nie trzeba nazywać jej w ogóle, można również zwracać arrow function. Funkcja ta, to nic innego, jak inaczej zapisany switch. Jej działanie jest następujące. Sprawdza, czy obiekt przekazany do handlers zawiera klucz o takiej nazwie, jak akcja, która została przekazana. Jeśli go odszuka, to go zwraca. A z racji tego, że każdy klucz zamiast wartości, ma przypisaną funkcję, to ją wywołuje i przekazuje do niej parametry state oraz action. Gdy taki klucz nie zostanie odnaleziony, wówczas zwraca domyślny state, bez żadnych zmian.

Teraz możemy stworzyć reducer, który będzie wyglądał tak:

export const userReducer = createReducer(null, {
  [SET_VISIBILITY_FILTER](state, action) {
    const newState = {...state, visibilityFilter: action.filter};
    return newState;
  },
  [SET_PRIVS] : setNewPrivs,
});

Pierwszy parametr, który jest tutaj nullem, to domyślny stan tego reducera (parametr initialState).

Tak stworzony reducer możemy spokojnie opakować w combineReducers w sposób, jaki robiliśmy to normalnie:

export const reducer = combineReducers({
    userReducer,
    messageReducer,
});
comments powered by Disqus