An introduction to Redux in React, including its workflow, core concepts—actions, reducers, and the store—and a complete example showing how to configure Redux for maintainable, extensible state management.
1. Why Use Redux?
- You do not need Redux when values and method calls remain within a simple parent-child component relationship.
- Consider Redux when one child updates shared state—organization data, for example—and other pages also depend on that data. Moving the state into Redux gives the application one place to manage it.
2. How Redux Works
3. Redux Workflow
4. Introduction to Redux
Redux is a predictable state container for JavaScript applications.
- Predictable
- State container
- For JavaScript applications
First, understand the three core parts of the Redux model: actions, reducers, and the store.
- Action: The payload that sends data from an application to the store. Actions are the store's only source of information. To request a state change from a local or remote component, dispatch an action.
- Reducer: An action describes what happened but does not change state itself. A reducer handles events according to
action.typeand calculates the next state.- Store: The object connecting actions and reducers. It is effectively a state tree containing the application's state. UI components can read the state they need from it.
React components do not directly mutate state values; they request changes through setState, in part because of the Virtual DOM model. In a component tree, a parent passes values to its children through props, and children cannot mutate their own props. That is why many introductory React examples keep state in the top-level component: it owns rerendering when data changes, while child components primarily display that data.
A common communication technique is to pass a parent method to a child through props. The child calls it in response to an event, indirectly changing the parent's state. This works in a small application but becomes awkward with deeply nested components or communication across separate branches of the tree.
In a complex component tree, application data and every operation that changes it can be separated from the UI. Together they form application-level state. To distinguish this persistent data from component-local state, its container is called the store.
The preceding explanation is adapted from an imooc.com Redux summary.
5. A Simple Redux Example
The original example uses create-react-app. Before starting, make sure Node.js and your package manager are installed.
npm install -g create-react-app
create-react-app redux-shoppingDelete everything in src except index.js, then replace its contents:
import { createStore } from "redux";
const reducer = function (state, action) {
return state;
};
const store = createStore(reducer);This code:
- Imports
createStore()from Redux. - Defines a reducer.
stateis the data currently in the store;actioncontains a requiredtypesuch asADD,UPDATE, orDELETE, plus an optionalpayloadcarrying update data. - Creates a Redux store from the reducer. Store data can be read directly but can only be updated through the reducer.
The current state is undefined. Give it an empty array as the default:
const reducer = function (state = [], action) {
return state;
};For multiple reducers, use Redux's combineReducers:
// src/index.js
import { createStore, combineReducers } from "redux";
const productsReducer = function (state = [], action) {
return state;
};
const cartReducer = function (state = [], action) {
return state;
};
const allReducers = {
products: productsReducer,
shoppingCart: cartReducer,
};
const rootReducer = combineReducers(allReducers);
const store = createStore(rootReducer);Next, add some initial data:
const initialState = {
cart: [
{ product: "bread 700g", quantity: 2, unitCost: 90 },
{ product: "milk 500ml", quantity: 1, unitCost: 47 },
],
};
const cartReducer = function (state = initialState, action) {
return state;
};
const store = createStore(rootReducer);
console.log("initial state: ", store.getState());Run npm start or yarn start, then inspect the state in the browser console.
The cart reducer currently does nothing. To manage cart state, define an add action:
const ADD_TO_CART = "ADD_TO_CART";
const cartReducer = function (state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
return {
...state,
cart: [...state.cart, action.payload],
};
default:
return state;
}
};A reducer handles different action types, commonly with a switch. When an ADD_TO_CART action is dispatched, this case calculates and returns a new state.
Now define an action creator. An action is a JavaScript object with a required type and optional payload:
function addToCart(product, quantity, unitCost) {
return {
type: ADD_TO_CART,
payload: { product, quantity, unitCost },
};
}Subscribe to changes, then dispatch several actions:
const unsubscribe = store.subscribe(() => console.log(store.getState()));
store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));
unsubscribe();The complete index.js is:
import { createStore, combineReducers } from "redux";
const productsReducer = function (state = [], action) {
return state;
};
const initialState = {
cart: [
{ product: "bread 700g", quantity: 2, unitCost: 90 },
{ product: "milk 500ml", quantity: 1, unitCost: 47 },
],
};
const ADD_TO_CART = "ADD_TO_CART";
const cartReducer = function (state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
return {
...state,
cart: [...state.cart, action.payload],
};
default:
return state;
}
};
function addToCart(product, quantity, unitCost) {
return {
type: ADD_TO_CART,
payload: { product, quantity, unitCost },
};
}
const rootReducer = combineReducers({
products: productsReducer,
shoppingCart: cartReducer,
});
const store = createStore(rootReducer);
console.log("initial state: ", store.getState());
const unsubscribe = store.subscribe(() => console.log(store.getState()));
store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));
unsubscribe();After saving, the browser refreshes and the newly added products appear in the console.
Splitting the Code into Modules
As index.js grows, organize it into a Redux project structure:
src/
├── actions
│ └── cart-actions.js
├── index.js
├── reducers
│ ├── cart-reducer.js
│ ├── index.js
│ └── products-reducer.js
└── store.js// src/actions/cart-actions.js
export const ADD_TO_CART = "ADD_TO_CART";
export function addToCart(product, quantity, unitCost) {
return {
type: ADD_TO_CART,
payload: { product, quantity, unitCost },
};
}// src/reducers/products-reducer.js
export default function (state = [], action) {
return state;
}// src/reducers/cart-reducer.js
import { ADD_TO_CART } from "../actions/cart-actions";
const initialState = {
cart: [
{ product: "bread 700g", quantity: 2, unitCost: 90 },
{ product: "milk 500ml", quantity: 1, unitCost: 47 },
],
};
export default function (state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
return {
...state,
cart: [...state.cart, action.payload],
};
default:
return state;
}
}// src/reducers/index.js
import { combineReducers } from "redux";
import productsReducer from "./products-reducer";
import cartReducer from "./cart-reducer";
export default combineReducers({
products: productsReducer,
shoppingCart: cartReducer,
});// src/store.js
import { createStore } from "redux";
import rootReducer from "./reducers";
export default createStore(rootReducer);// src/index.js
import store from "./store.js";
import { addToCart } from "./actions/cart-actions";
console.log("initial state: ", store.getState());
const unsubscribe = store.subscribe(() => console.log(store.getState()));
store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));
unsubscribe();The reorganized code behaves exactly as before. Now add update and delete actions.
// src/actions/cart-actions.js
export const UPDATE_CART = "UPDATE_CART";
export const DELETE_FROM_CART = "DELETE_FROM_CART";
export function updateCart(product, quantity, unitCost) {
return {
type: UPDATE_CART,
payload: { product, quantity, unitCost },
};
}
export function deleteFromCart(product) {
return {
type: DELETE_FROM_CART,
payload: { product },
};
}// src/reducers/cart-reducer.js
export default function (state = initialState, action) {
switch (action.type) {
case ADD_TO_CART:
return {
...state,
cart: [...state.cart, action.payload],
};
case UPDATE_CART:
return {
...state,
cart: state.cart.map((item) =>
item.product === action.payload.product ? action.payload : item,
),
};
case DELETE_FROM_CART:
return {
...state,
cart: state.cart.filter(
(item) => item.product !== action.payload.product,
),
};
default:
return state;
}
}Finally, dispatch both actions from index.js:
store.dispatch(updateCart("Flour 1kg", 5, 110));
store.dispatch(deleteFromCart("Coffee 500gm"));6. Using Redux in a Real Project
A user dispatches an action, the reducer calculates new state, and the view rerenders. One important question remains: what about asynchronous work?
When a reducer calculates state immediately after an action is dispatched, the flow is synchronous.
In real applications, many actions begin with asynchronous API requests:
When the reducer runs later, after asynchronous work completes, the overall flow is asynchronous.
redux-thunk is a popular middleware for asynchronous Redux actions.
- Import thunk:
import thunk from "redux-thunk". - Import middleware support:
import { createStore, applyMiddleware } from "redux". - Create the store with
createStore(reducer, applyMiddleware(thunk)). - The middleware is active once passed through
applyMiddleware. - Action creators may now return functions, giving synchronous and asynchronous actions a consistent calling style.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(counter, applyMiddleware(thunk));
export function addGunAsync() {
return (dispatch) => {
setTimeout(() => {
dispatch(addGUN());
}, 2000);
};
}For mapStateToProps, mapDispatchToProps, and connect, see:
https://www.cnblogs.com/hanmeimei/p/8820621.html
Related articles:
Discussion
Support the author