Refactoritza TodoMVC amb Redux Starter Kit
He treballat amb React durant més de dos anys. Vaig començar en un projecte gran que ja utilitzava Redux. Submergir-me en tant codi existent va ser aclaparador, especialment amb un framework desconegut. Amb el temps, em vaig sentir còmode i experimentat.
Recentment vaig descobrir Redux Starter Kit
de l'equip de Redux. Aquest conjunt d'eines simplifica el treball amb Redux. Una
eina, createReducer, segueix un patró que utilitzo des de fa temps. Redueix
codi repetitiu i accelera el desenvolupament, especialment en projectes nous.
Per aprendre aquestes eines, vaig migrar una base de codi existent amb Redux. Com a exemple, vaig triar l'omnipresent TodoMVC, específicament la versió del repositori de Redux.
Punt de partida
L'app té dos reducers principals: visibilityFilter i todos. Cadascun té les
seves pròpies accions, creadors d'accions i selectors.
Filtre de Visibilitat
Vaig començar amb el reducer més simple, després vaig passar al més complex.
Reducer
El reducer de l'exemple de Redux ja és simple i clar.
// reducers/visibilityFilter.jsimport { SET_VISIBILITY_FILTER } from "../constants/ActionTypes";import { SHOW_ALL } from "../constants/TodoFilters";export default (state = SHOW_ALL, action) => {switch (action.type) {case SET_VISIBILITY_FILTER:return action.filter;default:return state;}};
Redux Starter Kit proporciona createReducer per crear reducers. Com vaig
mencionar, ja utilitzo aquest patró i em resulta efectiu.
En lloc de crear un reducer amb una sentència switch case, passes l'estat
inicial com a primer paràmetre i un objecte que mapeja tipus d'acció a funcions
reducer ((state, action) => { /* codi del reducer */}).
Redueix codi repetitiu i gestiona automàticament el cas default amb
return state. El major benefici: millor llegibilitat.
Aquí està el reducer del filtre de visibilitat utilitzant createReducer:
// reducers/visibilityFilter.jsimport { createReducer } from "redux-starter-kit";import { SET_VISIBILITY_FILTER } from "../constants/ActionTypes";import { SHOW_ALL } from "../constants/TodoFilters";export default createReducer(SHOW_ALL, {[SET_VISIBILITY_FILTER]: (state, action) => action.filter,});
Creadors d'accions
Ara les accions. El filtre de visibilitat té una acció, SET_VISIBILITY_FILTER,
amb un creador simple:
// actions/index.jsimport * as types from "../constants/ActionTypes";/* ... Altres accions ...*/export const setVisibilityFilter = (filter) => ({type: types.SET_VISIBILITY_FILTER,filter,});
El conjunt d'eines proporciona createAction, que pren només el tipus d'acció
com a paràmetre i retorna un creador d'accions.
// actions/index.jsimport * as types from "../constants/ActionTypes";/* ... Altres accions ...*/export const setVisibilityFilter = createAction(types.SET_VISIBILITY_FILTER);
Aquest creador d'accions accepta paràmetres opcionals. Qualsevol argument es converteix en el payload de l'acció:
const setVisibilityFilter = createAction("SET_VISIBILITY_FILTER");let action = setVisibilityFilter();// { type: 'SET_VISIBILITY_FILTER' }action = setVisibilityFilter("SHOW_COMPLETED");// retorna { type: 'SET_VISIBILITY_FILTER', payload: 'SHOW_COMPLETED' }setVisibilityFilter.toString();// 'SET_VISIBILITY_FILTER'
Ara el filtre usa la clau payload en lloc de filter. Això requereix un petit
canvi en el reducer:
// reducers/visibilityFilter.jsimport { createReducer } from "redux-starter-kit";import { SET_VISIBILITY_FILTER } from "../constants/ActionTypes";import { SHOW_ALL } from "../constants/TodoFilters";export default createReducer(SHOW_ALL, {[SET_VISIBILITY_FILTER]: (state, action) => action.payload,});
Selectors
Els selectors són una de les millors eleccions en treballar amb React. Permeten refactoritzar l'estructura de l'estat sense canviar cada component que el consumeix.
El selector del filtre de visibilitat és directe:
// selectors/index.jsconst getVisibilityFilter = (state) => state.visibilityFilter;/* ... Altres selectors ...*/
Utilitzar createSelector afegeix una mica més de codi, però la recompensa
arriba aviat. Segueix llegint.
// selectors/index.jsimport { createSelector } from "redux-starter-kit";const getVisibilityFilter = createSelector(["visibilityFilter"]);/* ... Altres selectors ...*/
Slices
Fins ara, hem reemplaçat funcions simples amb altres més simples utilitzant
diversos creadors. Ara ve el veritable poder del conjunt d'eines: createSlice.
createSlice accepta un estat inicial, funcions reducer i un nom de slice
opcional. Genera automàticament creadors d'accions, tipus d'accions i selectors.
Ara podem descartar tot el codi anterior.
Crear un slice per al filtre de visibilitat és net i elimina codi repetitiu significatiu.
// ducks/visibilityFilter.jsimport { createSlice } from "redux-starter-kit";export default createSlice({slice: "visibilityFilter",initialState: SHOW_ALL,reducers: {setVisibilityFilter: (state, action) => action.payload,},});
El resultat és un sol objecte amb tot el necessari per treballar amb Redux:
const reducer = combineReducers({visibilityFilter: visibilityFilter.reducer,});const store = createStore(reducer);store.dispatch(visibilityFilter.actions.setVisibilityFilter(SHOW_COMPLETED));// -> { visibilityFilter: 'SHOW_COMPLETED' }const state = store.getState();console.log(visibilityFilter.selectors.getVisibilityFilter(state));// -> SHOW_COMPLETED
Veure tots els canvis fins ara en aquest commit.
Todos
El reducer de todos és més complex, així que explicaré el resultat final en lloc de cada pas. Veure el codi complet aquí.
Primer, definir l'estat inicial:
// ducks/todos.jsconst initialState = [{text: "Use Redux",completed: false,id: 0,},];
Per millorar la llegibilitat, vaig extreure cada acció del reducer en la seva pròpia funció:
// ducks/todos.jsconst addTodo = (state, action) => [...state,{id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,completed: false,text: action.payload.text,},];const deleteTodo = (state, action) =>state.filter((todo) => todo.id !== action.payload.id);const editTodo = (state, action) =>state.map((todo) =>todo.id === action.payload.id? { ...todo, text: action.payload.text }: todo,);const completeTodo = (state, action) =>state.map((todo) =>todo.id === action.payload.id? { ...todo, completed: !todo.completed }: todo,);const completeAllTodos = (state) => {const areAllMarked = state.every((todo) => todo.completed);return state.map((todo) => ({...todo,completed: !areAllMarked,}));};const clearCompleted = (state) =>state.filter((todo) => todo.completed === false);
Ara combina'ls en un slice:
// ducks/todos.jsconst todos = createSlice({slice: "todos",initialState,reducers: {add: addTodo,delete: deleteTodo,edit: editTodo,complete: completeTodo,completeAll: completeAllTodos,clearCompleted: clearCompleted,},});
Per defecte, els selectors de createSlice simplement retornen valors de
l'estat (ex: todos.selectors.getTodos). Aquesta aplicació necessita selectors
més complexos.
Per exemple, getVisibleTodos necessita el filtre de visibilitat i els todos.
createSelector pren un array de selectors (o rutes de l'estat) com a primer
paràmetre i una funció implementant la lògica de selecció com a segon.
// ducks/todos.jsconst { getVisibilityFilter } = visibilityFilter.selectors;todos.selectors.getVisibleTodos = createSelector([getVisibilityFilter, todos.selectors.getTodos],(visibilityFilter, todos) => {switch (visibilityFilter) {case SHOW_ALL:return todos;case SHOW_COMPLETED:return todos.filter((t) => t.completed);case SHOW_ACTIVE:return todos.filter((t) => !t.completed);default:throw new Error("Filtre desconegut: " + visibilityFilter);}},);todos.selectors.getCompletedTodoCount = createSelector([todos.selectors.getTodos],(todos) =>todos.reduce((count, todo) => (todo.completed ? count + 1 : count), 0),);
Vaig afegir els nous selectors a l'objecte todos.selectors, mantenint tots els
selectors en un sol lloc.
Crear Store
La llibreria també proporciona configureStore i getDefaultMiddleware.
configureStore embolcalla el createStore de Redux. Ofereix la mateixa
funcionalitat amb una API més neta--habilitar eines de desenvolupador requereix
només un booleà.
getDefaultMiddleware retorna
[immutableStateInvariant, thunk, serializableStateInvariant] en
desenvolupament i [thunk] en producció.
redux-immutable-state-invariant: Detecta mutacions en reducers durant el dispatch i entre dispatches (en selectors o components).serializable-state-invariant-middleware: Comprova estat i accions per a valors no serialitzables com funcions i Promeses.
// store.jsimport { configureStore, getDefaultMiddleware } from "redux-starter-kit";import { combineReducers } from "redux";import { visibilityFilter, todos } from "./ducks";const preloadedState = {todos: [{text: "Use Redux",completed: false,id: 0,},],};const reducer = combineReducers({todos: todos.reducer,visibilityFilter: visibilityFilter.reducer,});const middleware = [...getDefaultMiddleware()];export const store = configureStore({reducer,middleware,devTools: process.env.NODE_ENV !== "production",preloadedState,});
Pensaments finals
Redux Starter Kit redueix codi repetitiu, fent el codi més net i fàcil d'entendre. També accelera el desenvolupament.
Codi Font: https://github.com/magarcia/todomvc-redux-starter-kit