Redux started
Redux
Is a state manager
Redux workflow
- Create a Store to Store data
Use createStore to create a Store to Store state in the app. There is only one Store in the app
import { createStore } from "redux";
Create a Reducer to define the change rules in state
const countReducer = function(count = 0, action) {
switch(action.type) {
case 'ADD':
return count + 1;
case 'MINUS':
return count - 1;
default:
returncount; }}// Create a store to store state
const store = createStore(countReducer);
export default store;
Copy the code
-
The Reducer in the Store initializes State and defines State modification rules
Reducer is a pure function that takes Action and the current state as arguments and returns a new state. When creating a Store, pass Reducer as a parameter to createStore. If the state structure is complex, it can be considered to split multiple Reducer and synthesize using combineReducer.
import { combineReducers, createStore } from "redux";
// Reducer pure function
const countReducer = function (count = 0, action) {
console.log("action", action);
switch (action.type) {
case "ADD":
return count + 1;
case "MINUS":
return count - 1;
default:
returncount; }};const userReducer = function (name = "Caramelized melon seeds", action) {
switch (action.type) {
case "ENGLISH":
return ` ENGLISH:${name}`;
case "CHINESE":
return ` English:${name}`;
default:
return Anonymous; }};// You can rename reducer in combineReducers
const store = createStore(combineReducers({userReducer, countReducer}));
export default store;
Copy the code
- The user submits changes to the data by dispatching an Action
State data changes are triggered using a dispatch submission Action alone, and view updates are not triggered.
import { Component } from "react";
import store from "./store";
export default class ReduxPage extends Component {
componentDidMount() {
// Store changes cannot trigger render in real time, we need to subscribe to listen to state changes, forcing trigger update
store.subscribe(() = > {
this.forceUpdate();
})
}
handleAdd = () = > {
// Submit action via dispatch: {type: 'ADD'}
store.dispatch({ type: 'ADD' });
}
render() {
return (
<div>{store.getState()}<br />
<button onClick={this.handleAdd}>increase</button>
<button onClick={this.handleMinus}>To reduce</button>
</div>); }}Copy the code
- The Store automatically calls the Reducer and sends the current State and Action to the Reducer. The Reducer returns the new State based on the input Action type
Reducer returns a new state based on the type submitted by the action
const countReducer = function (count = 0, action) {
console.log("action", action);
switch (action.type) {
case "ADD":
return count + 1;
case "MINUS":
return count - 1;
default:
returncount; }};Copy the code
Write a story
createStore
createStore(reducer, enhancer)
Creates a store for storing all data in an application.
Parameters:
reducer
(Function) : pure functionsenhancer
(Function):Store enhancer is a higher order Function that combines Store Creator and returns a new enhanced Store Creator
Return value: (store) Holds the state object for all applications. Store has several methods: getState(), Dispatch (action),subscribe(listener), and replaceReducer(nextReducer)
/** * Create a store to store all data in the application *@param {Function} Reducer is a pure function *@returns Return objects that hold all states: getState, dispatch, subscribe methods */
export default function createStore(reducer) {
let currentState;
let currentListeners =[]; // To save all listeners
/** * This is the only way to trigger a state change *@returns Returns the current state */
const getState = () = > {
return currentState;
}
/** * This is the only way to trigger a state change * to call the Store's reduce function synchronously with the result of the current getState() and the incoming action *@param {Object} Action A generic function that describes application changes *@returns Action */ to dispatch
const dispatch = (action) = > {
currentState = reducer(getState(), action);
currentListeners.map(listener= > listener());
return action;
}
// Perform a dispatch to initialize data on the Reducer and return the default data on the Reducer
dispatch({ type: 'redux/source' });
const subscribe = (listener) = > {
if (typeoflistener ! = ='function') {
throw new Error('Listener must be a function! ');
}
// Store all listener functions in currentListener
currentListeners.push(listener);
return () = >{ currentListeners = []; }}return {
getState,
dispatch,
subscribe
}
}
Copy the code
combineReducer
As the application becomes more and more complex, the Reducer function can be split into separate functions (that is, multiple reducer) that are part of each Reducer management state.
CombineReducer: Receives objects combined by multiple reducers and returns a new Reducer. Each Reducer has its own part in the reducer management state
/** * Merge an object made up of different reducer functions as values into a final Reducer function * and then call the createStore method * on this reducer@param {Object} Reducers receive objects * that are combined from multiple reducers@returns Return a Reducer */ from the Reducer combination
export default function combineReducers(reducers) {
if (Object.prototype.toString.call(reducers) ! = ='[object Object]') {
throw new Error('combineReducers request to pass in an object composed by reducer ');
}
// Return a reducer: parameters state and action
return (state = {}, action) = > {
Reducer returns a newState. The latest newState was obtained by executing the reducer in the reducers
let newState = {};
// Execute all reducer to obtain the latest data and update state
for (let key in reducers) {
let reducer = reducers[key];
newState[key] = reducer(state[key], action);
}
returnnewState; }}Copy the code
applyMiddleware
The purpose of applyMiddleware is to enhance dispatch and make it easy for external custom middleware to expand
The first version implements logging
1. Require a log to be generated when the user submits an action
// While enforcing dispatch, the basic functions of Dispatch are performed inside the function
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = action= > {
console.log('Start execution${action.type}`);
next(action);
console.log('Completion of execution${action.type}`);
}
Copy the code
The second edition records exceptions
There is another requirement to record the cause of every data error, so we extend dispatch
const store = createStore(reducer);
const next = store.dispatch;
// Record exceptions on the basis of logging
store.dispatch = action= > {
try{
console.log('Store state tree'Store. GetState ()); next(action); }catch(e) {
console.log(e); }}Copy the code
Collaboration with multi-functional middleware
Output the current state tree if the latest requirement requires that exceptions be logged as well as logged. You need to merge the two functions
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = action= > {
try {
console.log('Store state tree'Store. GetState ());// Log
console.log('Start execution${action.type}`);
next(action);
console.log('Completion of execution${action.type}`);
} catch(e){
console.log('e', e); }}Copy the code
If new requirements continue to come in, manually change the dispatch extension each time. Therefore, we need to consider the implementation of strong scalability of multi-middleware cooperation mode
1. Extract loggerMiddleware for logging
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = action= > {
console.log('Start execution${action.type}`);
next(action);
console.log('Completion of execution${action.type}`);
}
store.dispatch = loggerMiddleware;
Copy the code
2. The errorMiddleware that records exceptions is extracted and the data in store is recorded in the normal process
const store = createStore(reducer);
const next = store.dispatch;
const errorMiddleware = action= > {
try {
console.log('Store state tree'Store. GetState ()); loggerMiddleware(action); }console.log('e', e);
}
store.dispatch = errorMiddleware;
Copy the code
3. Existing problems: ErrorMiddleware’s executors ** Next died as loggerMiddleware, and loggerMiddleware’s executors died as Next ** as Store. dispatch. Both are strongly correlated and over-coupled. Our requirement is that any middleware should be able to be used together
const store = createStore(reducer);
const next = store.dispatch
// loggerMiddleware needs to receive a next to level up
const loggerMiddleware = next= > action= > {
console.log('Start execution${action.type}`);
next(action);
console.log('Completion of execution${action.type}`);
}
const errorMiddleware = next= > action= > {
try {
console.log('Store state tree'Store. GetState ()); next(action); }catch(e) {
console.log('e', e); }}const store = createStore(reducer);
store.dispatch = errorMiddleware(loggerMiddleware(next))
Copy the code
4. Modular division of middleware
Different middleware is placed in different files. The current loggerMiddleware and errorMiddleware middleware are still strongly associated with store and continue to scale up, passing store as a parameter
// loggerMiddleware.js
// loggerMiddleware needs to receive another store to level up again
export default function loggerMiddleware = store= >next= >action= >{
console.log('Start execution${action.type}`);
next(action);
console.log('Completion of execution${action.type}`);
}
// errorMiddleware.js
export default function errorMiddleware = store= >next= >action= >{
try {
console.log('Store state tree'Store. GetState ());// 当前顺序 errorMiddleware中的next为loggerMiddleware
next(action);
} catch(e) {
console.log('e', e); }}// store.js
const store = createStore(reducer);
const next = store.dispatch;
const newErrorMiddleWare = errorMiddleWare(store);
const newLoggerMiddleWare = loggerMiddleWare(store);
store.dispatch = newErrorMiddleWare(newLoggerMiddleWare(next));
Copy the code
Optimization of middleware usage
According to the middleware implemented in the previous section, but the use of middleware is not friendly, and the details of middleware need to be encapsulated. In fact, we don’t need to know the specifics of middleware, just that middleware is there. We can encapsulate the details by extending createStore.
Store. Dispatch = errorMiddleware(loggerMiddleware(Next)) middleware merge, which we can implement with a compose function.
// store.js
const store = createStore(reducer);
const next = store.dispatch;
function compose(. funcs) {
if (funcs.length === 0) {
return arg= > arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) = > (. args) = >a(b(... args))); } store.dispatch = compose(errorMiddleware(store), loggerMiddleware(store))(next);Copy the code
Currently, although compose is used to encapsulate the middleware, details such as store.dispatch are still exposed and we can continue to encapsulate the functionality of the upgraded createStore.
Implement applyMiddleware: Receiving middleware returns a store of data
// applyMiddleware.js
/** *@param {... any} Middlewares middleware */
export default function applyMiddleware(. middlewares) {
/ * * * *@param {... any} Funcs Middleware *@returns Combine the middleware and return a new function */
function compose(. funcs) {
console.log("funcs", funcs);
if (funcs.length === 0) {
return (arg) = > arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(
(a, b) = >
(. args) = >a(b(... args)) ); }return store= > {
// Enhance dispatch in store
let dispatch = store.dispatch;
// Middleware parameters
let params = {
getState: store.state,
dispatch: (. args) = >dispatch(... args), };// Pass parameters to each middleware
let middlewaresChain = middlewares.map((middleware) = > middleware(params));
/ / rewrite dispatchdispatch = compose(... middlewaresChain)(dispatch);return {
...store,
dispatch,
};
};
}
// store.js
const store = createStore(reducer);
const stongerStore = applyMiddlewares(errorMiddleware, loggerMiddleware)(store);
Copy the code
Some optimizations for applyMiddleware still have problems, which can cause stores to be created simultaneously. CreateStore can be enhanced by modifying it to use applyMiddleware as a parameter
// createStore.js
/** * Create a store to store all data in the application *@param {Function} Reducer is a pure function *@returns Return objects that hold all states: getState, dispatch, subscribe methods */
export default function createStore(reducer, enhancer) {
let currentState;
let currentListeners =[]; // To save all listeners
if (enhancer) {
return enhancer(createStore)(reducer);
}
/** * This is the only way to trigger a state change *@returns Returns the current state */
const getState = () = > {
return currentState;
}
/** * This is the only way to trigger a state change * to call the Store's reduce function synchronously with the result of the current getState() and the incoming action *@param {Object} Action A generic function that describes application changes *@returns Action */ to dispatch
const dispatch = (action) = > {
currentState = reducer(getState(), action);
currentListeners.map(listener= > listener());
return action;
}
// Perform a dispatch to initialize data on the Reducer and return the default data on the Reducer
dispatch({ type: 'redux/source' });
const subscribe = (listener) = > {
if (typeoflistener ! = ='function') {
throw new Error('Listener must be a function! ');
}
// Store all listener functions in currentListener
currentListeners.push(listener);
return () = >{ currentListeners = []; }}return {
getState,
dispatch,
subscribe
}
}
Copy the code
We also need to modify some of the receive parameters for applyMiddleware
// applyMiddleware.js
/** *@param {... any} Middlewares middleware */
export default function applyMiddleware(. middlewares) {
/ * * * *@param {... any} Funcs Middleware *@returns Combine the middleware and return a new function */
function compose(. funcs) {
console.log("funcs", funcs);
if (funcs.length === 0) {
return (arg) = > arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(
(a, b) = >
(. args) = >a(b(... args)) ); }return (createStore) = > (reducer) = > {
// Enhance dispatch in store
const store = createStore(reducer);
let dispatch = store.dispatch;
// Middleware parameters
let params = {
getState: store.state,
dispatch: (. args) = >dispatch(... args), };// Pass parameters to each middleware
let middlewaresChain = middlewares.map((middleware) = > middleware(params));
/ / rewrite dispatchdispatch = compose(... middlewaresChain)(dispatch);return {
...store,
dispatch,
};
};
}
Copy the code
After putting applyMiddleWare enhancements into createStore, you can create a store just by running createStore to generate one
// store.js
const store = createStore(reducer, applyMiddleWare(errorMiddleWare, toggerMiddleWare));
Copy the code
bindActionCreator
The main function of bindActionCreator is to hide dispatch and action creator, and bindActionCreator to dispatch for external direct invocation.
Rarely used, use scenarios are relatively few; Used in React-redux
/** * convert an object whose value is a different action creator to an object with the same key. * Wrap each Action Creator with Dispatch at the same time so that they can be called directly * * actionCreators can also be an array, but is not very user-friendly *@param {Function | Object} actionCreators
* @param {Function} dispatch
*/
function bindActionCreator (actionCreator, dispatch) {
// Returns actionCreator enhanced with Dispatch
// args: The argument passed in by Action Creator
return (. args) = >dispatch(actionCreator(... args)); }export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if (typeofactionCreators ! = ='object' || actionCreators === null) {
throw new Error('actionCreators must be Function or Object');
}
let boundActionCreators = {};
for(let key in actionCreators) {
let actionCreator = actionCreators[key];
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators;
}
Copy the code