🔨 redux implementationThe warehouse link

1. The story synopsis

  • React is one of the communication methods used by the REACT component to make it predictable and controllable when the component state changes
  • The entire application state exists in a single store
  • The changed state can only be sent to the store through the component dispatch action, which is received by the pure reducer function and returns the new state
  • Components refresh their views by publishing subscriptions

2. Flow chart of Redux communication mode 👇👇👇

3. Simple implementation of REdux

3.1 Redux generates a store through createStore

** ** redux * 1. Action: an action created for an object by a user, which refers to what action takes place, analogous to an instruction * 2. Reducer: a pure function that receives the actions sent by the user and the last updated state and returns the latest state * 4.store: 4.1. getState: returns the current state * 4.2. dispatch: dispatches the state and triggers the subscription event * 4.3. subscribe: Function createStore(reducer){// Add currentState; Let currentListeners = []; Function getState(){return currentState; CurrentState = reducer(currentState, action); CurrentListeners. ForEach (lis => lis()); } function subscribe(listener){// currentListeners. Return () => {currentListeners = currentListeners. Filter (lis => lis! == listener); // Const index = currentListeners. IndexOf (listener) // currentListeners. 1)}} // initialize state dispatch({}) // return the corresponding store method return {getState, dispatch, subscribe}} ' 'Copy the code

3.2 Testing the implementation of REdux, again using counters as examples (if this example is identical, it is absolutely necessary 😜)

3.2.1 create store/index. Js

``` import { createStore } from '.. /redux-mini'; State const countReducer = (state = initState, {type, payload }) => { switch(type){ case 'ADD': return state + payload case 'MINUS': return state - payload default: return state; Const store = createStore(countReducer); export default store; ` ` `Copy the code

3.2.2 Create the reduxPage.js component and import it in index.js

``` // ReduxPage.js import { Component } from 'react'; // store import store from '.. /store'; Export Default Class ReduxPage extends Component{componentDidMount(){this.cancel = store.subscribe(() => { // Update view this.forceUpdate()})} componentWillUnmount(){// Unsubscribe this.cancel && this.cancel(); } handleAdd = () => {// Trigger the dispatch store.dispatch({type: 'ADD', payload: 1})} handleMinus = () => {// Trigger dispatch store. Dispatch ({type: 'MINUS', payload: 1 }) } render(){ return ( <div> <h2>reduxPage</h2> <div>count:{ store.getState().count }</div> <button onClick={this.handleAdd}>+</button> <button onClick={this.handleMinus}>-</button> </div> ) } } // index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import ReduxPage from './pages/ReduxPage'; ReactDOM.render( <React.StrictMode> <ReduxPage /> </React.StrictMode>, document.getElementById('root') ); ` ` `Copy the code

As shown in the figure, the view changes after multiple actions are sent.

The status is printed on the browser console, first printing undefined, as dispatch

During initialization in createStore

4. Implementation of combinReducers in REdux

Usually, in a project, there must be more than one state that needs to be changed, but more than one state, with different information for each state.

Put it in a Reducer, it will be very large and the readability will become poor. In this case, we need to create multiple reducer, corresponding to different states.

As shown below.

Const multiState = {MSG: 'state ', count: 0, bool: true } const multiReducer = (state = multiState, { type, payload }) => { switch(type){ case 'MSG': return Object.assign(state, { ... state, msg: payload }) case 'COUNT': return Object.assign(state, { ... state, count: payload + 1 }) case 'BOOL': return Object.assign(state, { ... state, bool: payload }) default: return state; }} ` ` `Copy the code

There are three attributes in the state that need to be changed. There is no connection between each attribute and the way of change may be different, so you need to create them

There are multiple reducers, and each Reducer returns the corresponding attribute status. In Redux, however, only orders are passed in createStore

Therefore, we need to merge the created multiple reducer into a single reducer, which is called combineReducers.

// combineReducers receives a reducer object collection // The attributes in the reducers object passed in must be the same as the state attribute name, Function combineReducers(reducers){return function Combination (state = {}, action){ const reducerKeys = Object.keys(reducers); // Let hasChanged = false; const nextState = {}; for(let i = 0; i < reducerKeys.length; I ++){// Get the current state attribute const Key = reducerKeys[I]; // Obtain the reducer const reducer = reducers[key]; // Get the previous state const previousStateKey = state[key]; // Obtain the latest state corresponding to the current state attribute const nextStateKey = Reducer (previousStateKey, action); NextState [key] = nextStateKey; hasChanged = hasChanged || nextStateKey ! == previousStateKey } hasChanged = hasChanged || reducerKeys.length ! == Object.keys(state).length; // Return old state if the state has not changed, otherwise return new hasChanged? nextState : state; }} ` ` `Copy the code

In addition to TS and some exception handling, the above code is basically implemented by redux source code moved over,

Teacher Ruan Yifeng’s blog is implemented through reduce: link to 👉

To implement the code as follows: 👇👇👇

``` const combineReducers = reducers => { return (state = {}, action) => { return Object.keys(reducers) .reduce((newState, key) => { newState[key] = reducers[key](state[key], action); // console.log('newState', state) return newState; }, {})}} ' 'Copy the code

4.1 combineReducers test

Modify the store/index. Js

Const multiState = {MSG: 'state ', count: 0, bool: 'bool' } const msgReducer = (state = multiState.msg, { type, payload }) => { switch(type){ case 'MSG': return state + payload; default: return state; } } const couReducer = (state = multiState.count, { type, payload }) => { switch(type){ case 'ADD': return state + payload; case 'MINUS': return state - payload; default: return state; } } const boolReducer = (state = multiState.bool, { type, payload }) => { switch(type){ case 'BOOL': return state + payload; default: return state; } // Pass the reducer function to generate store const rootReducer = combineReducers({MSG: msgReducer, count: couReducer, bool: boolReducer, }) const store = createStore(rootReducer); export default store; HandleMSG = () => {store.dispatch({type: 'MSG', payload: HandleCOUNT = () => {store. Dispatch ({type: 'ADD', payload: 1 }) } handleBOOL = () => { store.dispatch({ type: 'BOOL', payload: 'bool' }) } render(){ return ( <div> <h2>reduxPage</h2> <div>msg:{ store.getState().msg }</div> <div>cou:{ store.getState().cou }</div> <div>bool:{ store.getState().bool }</div> <button onClick={this.handleMSG}>msg</button> <button onClick={this.handleCOUNT}>count</button> <button onClick={this.handleBOOL}>bool</button> </div> ) } ```Copy the code

Results:

Implement the applyMiddleware middleware mechanism

At this point a basic Redux is implemented, but there is a problem. The dispatch function currently implemented in Redux only supports object form,

They are synchronous and do not support asynchronous methods. In a real project, there will be interface requests, separated logging, crash reporting, and so on, which requires a middleware mechanism to enforce the dispatch function.

Using applyMiddleware to invoke the enhanced Dispatch function in Redux,

The origin of applyMiddleware is described in 👉 Redux.

  • ApplyMiddleware exposes only a subset of store APIS to Middleware: Dispatch (Action) and getState().

  • It’s a clever way to make sure that if you call Store.dispatch (action) instead of next(action) in middleware, This action will iterate through the entire middleware chain again, including the current middleware. This is very useful for asynchronous Middleware, as we mentioned in the previous section.

  • To make sure you can only use Middleware once, it works on createStore(), not the store itself. So its signature is not (store, middlewares) => store, but (… Middlewares) => (createStore) => createStore.

As summarized in the previous three paragraphs, this middleware mechanism is implemented by passing createStore as a parameter to be called in applyMiddleware

CreateStore gets the Store, then collects some subset of the Store API and enhances the Dispatch function. Well, that’s it 🤔

Next go to createstore.js to modify this function. Two modifications have been made on the original basis. Here are the screenshots directly:

After modifying the above functions, create a new applyMiddleware file to implement the middleware mechanism (see the code comment for details)

``` /** * * @param {... Any} middlewares applyMiddleware(thunk, logger) */ export default function applyMiddleware(... Middlewares){return function (createStore){ Const store = createStore(reducer); // Get the dispatch function let dispatch = store.dispatch; Const midAPI = {getState: store.getState, dispatch: (action,... args) => dispatch(action, ... Args)} // forEach for redux: // forEach for redux: // Middlewares. ForEach (Middleware => {// Middleware means logger or thunk redux middleware will eventually return a new one The dispatch function changes the original dispatch, and // the changed dispatch is passed in as an argument, so layer upon layer, Dispatch = middleware(midAPI)(dispatch) // Each middleware form looks something like this, // Dispatch = ({getState, dispatch}) => (dispatch) => action => {} M2, M3] // The result is M3(M2(M1(dispatch)) from left to right. For compose, M1(M3(dispatch))} from right to left. Enhanced Dispatch return {... store, dispatch } } } } ```Copy the code

The applyMiddleware function is implemented, so let’s see if it works

Start by installing three Redux middleware

```
yarn add redux-thunk redux-logger redux-pormise -D
```
Copy the code

Modify the store/index.js file

Three middleware and applyMiddleware functions are introduced

Modify the createStore function

Reduxpage.js needs to be changed next

The final result

You can see that Redux in the browser supports state changes in the form of functions and promises, while in the browser console

Prints out the corresponding log ✌

6. Implement bindActionCreators

Most of the redux API implementation above has been implemented, leaving only one remaining API: bindActionCreators. This function is mainly used with react-redux

Upgrade a function without dispatch to dispatch(func(… Args)), note that func returns action

' '// Implemented bindActionCreators, 🙂 export default Function bindActionCreators(Creators, Dispatch){const bindCreators = {} for(const key in creators){if(Typeof Creators [key] === 'function'){// Accept the creators here The dispatch function bindCreators[key] = bindActionCreator(Creators[key], Dispatch); } } return bindCreators; } // Add the dispatch function bindActionCreator(Creators, dispatch){return (Creators, dispatch). args) => dispatch(creators(... args)) } ```Copy the code

Now that all of redux’s apis are implemented, we can manually implement the three middleware we installed above and test the simplified version of Redux

🔨 Redux middleware implementation

1. Introduction to middleware

The representation of redux middleware is described in the Redux Chinese documentation,

When you implement applyMiddleware above, you know that middleware looks something like this

({getState, Dispatch}) => (next) => (action) => {/ Middleware to handle /}

For ease of understanding, the following implementation does not take the form of arrow functions

2. Middleware Redux-Thunk implementationThe original source

// Redux-thunk middleware allows dispatch to support functions and asynchronous forms, mainly to determine the type of incoming action, Function thunk({getState, Dispatch}){return function(next){return function(action){if(typeof action === 'function'){ Dispatch and getState return action(dispatch, getState)} return next(action)}}} ' 'Copy the code

3. Middleware Redux-promise implementationThe original source

// This middleware allows dispatch to support the promise form, which is the same as redux-thunk, but is a call function, // yarn add is-promise -d // import isPromise from 'is-promise' Function promise({dispatch}){return function (next){return function (action){if(isPromise(action)){// Determine the action // The payload is a promise, and the payload is a promise. // The payload is a promise. Return action.then(dispatch)} return next(action); }}} ` ` `Copy the code

4. Realization of middleware Redux-LoggerThe original source

// This middleware is responsible for printing logs, Function Logger ({getState}){return function(next){return function(action){logger({getState}){return function(action){ console.log('**********************************'); Console. log(action.type + 'Executed! '); // Get the previous state const prevState = getState(); console.log('prev state', prevState); ReturnedValue = next(action); const nextState = getState(); console.log('next state', nextState); console.log('**********************************'); return returnedValue } } } ```Copy the code

5. Middleware testing

Next, see if the implemented middleware works properly,

Modify store/index.js to write all three middleware functions to this file, and remember to include IS-Promise

The following figure shows the result: 👇👇👇

You can see that with the introduction of middleware that you implement, Dispatch still supports functions and promise forms, and the browser prints logging,

✌, although not as pretty

conclusion

Here is a simple version of Redux including some middleware on the basic implementation, find what went wrong or have a problem welcome you to leave a message in the comments section

Warehouse address

Refer to the link

1. Ruan Yifeng -Redux Tutorial (I) : Basic Usage

2. Advanced Tutorial for Redux

3. Redux Chinese documents