preface

What is a redux?

Redux is a state manager

Simple state manager

Redux is a state manager, so what is state?

State is data, and a state manager is a data manager. So let’s implement a simple state manager.

1. The state manager has to have a place to store state. Since we call it a state manager, let’s define onestateVariables, as a place to store state.
let state = {
  name: 'Legend of Chaos',}// Use the down state
console.log(state.name)
Copy the code
2. Given a place to store state, there has to be a way to modify state, which we’ll callupdateState.
let state = {
  name: 'Legend of Chaos',}// Use the down state
console.log(state.name) // Output :' Legend of Chaos'

const updateState = (newName) = > {
  state.name = newName
}
// Update the status
updateState('Legend of Chaos 2')
// Print the status again
console.log(state.name) // Output :' Legend of Chaos 2'
Copy the code
3. With a place to store state and a way to modify state, something feels missing. Let’s think about it. After the state is modified, if the place using the state does not know that the state has changed, then the place using the state is still using the old state. We currently have no way to tell the place using the state that the state has been updated.

Ok, so we have a problem, so we solve the problem, and we can use publish-subscribe to solve the problem.

let listeners = []
let state = {
  name: 'Legend of Chaos',}// Use the down state
console.log(state.name) // Output :' Legend of Chaos'

const updateState = (newName) = > {
  state.name = newName
  listeners.forEach(listerner= > listerner())
}

const subscribe = (listener) = > {
  listeners.push(listener)
}

// Subscribe to the state
subscribe((a)= > {
  // Print state.name when state is updated
  console.log('The latest name is:${state.name}`) // Output :' The latest name is: Legend of Chaos 2'
})

// Update the status
updateState('Legend of Chaos 2')
// Print the status again
console.log(state.name) // Output :' Legend of Chaos 2'
Copy the code
4. SolvestateData subscription problem, let’s look at the implementation of this simple state manager, is not update the stateupdateStateMethods can only be updatedstate.nameWe expect a status updateupdateStateMethods can be updatedstateAll the data stored in, not justname. So let’s solve this problem.
let listeners = []
let state = {
  name: ' '.like: ' ',}const updateState = (newState) = > {
  state = newState
  listeners.forEach(listerner= > listerner())
}

const subscribe = (listener) = > {
  listeners.push(listener)
}
Copy the code

Okay, so we’ve updated the updateState method, so let’s try out our new state manager

let listeners = []
let state = {
  name: ' '.like: ' ',}const updateState = (newState) = > {
  state = newState
  listeners.forEach(listerner= > listerner())
}

const subscribe = (listener) = > {
  listeners.push(listener)
}

subscribe((a)= > {
  // Print state when state is updated
  console.log(state) }) updateState({ ... state,name: 'Legend of Chaos'}) updateState({ ... state,like: 'Play table tennis'
})
Copy the code

The output is:

> {name: "Legend of Chaos".like: ""}

> {name: "Legend of Chaos".like: "Play table tennis"}
Copy the code
5. The upgradeupdateStateMethod, let’s encapsulate our state manager again, the current onelistenersandstateThey’re all out there, open to change
const createStore = (initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const updateState = (newState) = > {
    state = newState
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    updateState,
    subscribe,
  }
}
Copy the code

Let’s use the state manager to manage more data:

const createStore = (initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const updateState = (newState) = > {
    state = newState
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    updateState,
    subscribe,
  }
}


let initState = {
  name: ' '.sex: ' '.age: ' '.like: ' '.friend: ' ',}let { getState, updateState, subscribe } = createStore(initState)

subscribe((a)= > {
  console.log(getState()) }) updateState({ ... getState(),name: 'Legend of Chaos', }) updateState({ ... getState(),sex: 'male', }) updateState({ ... getState(),age: '25', }) updateState({ ... getState(),like: 'Play badminton', }) updateState({ ... getState(),friend: 'his',})Copy the code

Run the code, and the output is:

> {name: "Legend of Chaos".sex: "".age: "".like: "".friend: ""}

> {name: "Legend of Chaos".sex: "Male".age: "".like: "".friend: ""}

> {name: "Legend of Chaos".sex: "Male".age: "25".like: "".friend: ""}

> {name: "Legend of Chaos".sex: "Male".age: "25".like: "Play badminton".friend: ""}

> {name: "Legend of Chaos".sex: "Male".age: "25".like: "Play badminton".friend: "Aaron"}
Copy the code

At this point we have completed a simple state manager. Far from being a true Redux, let’s continue refining our state manager.

Planned state manager

We use the above us to achieve the status of the manager to implement a since the increase since decreases program, expect the count is a since the increase | since the reduced number of types of values:

const createStore = (initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const updateState = (newState) = > {
    state = newState
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    updateState,
    subscribe,
  }
}



let initState = {
  count: 0
}
let { getState, updateState, subscribe } = createStore(initState)

console.log(getState())

subscribe((a)= > {
  console.log(getState())
})

/ / since the increaseupdateState({ ... getState(),count: getState().count+1,})/ / the decrementupdateState({ ... getState(),count: getState().count- 1,})// Change it arbitrarilyupdateState({ ... getState(),count: 'Silly.',})Copy the code

Run the program, and the output is:

> {count: 0}

> {count: 1}

> {count: 0}

> {count: "What a fool!"}
Copy the code

We can see from the output that count has been changed to a string, and we expect count to be number. The solution to this problem is that when we change the state, we change the state as expected.

How do we make it so that when we change the state, we change it as expected?

We can do this:

1. We define a state modification behavior (action)
2. We make a status modification plan (reducer),reducerWill receive since the last updatestateandupdateStateThe latest to pass inaction.reducerAccording to theactionTo modify thestateAfter modification, return to the lateststatetoupdateState
3. updateStateMethod receivesaction,actiontellreducer.

Based on these three points, let’s modify our state manager

const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const updateState = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    updateState,
    subscribe,
  }
}
Copy the code

Let’s try using the new state manager to increment and decrement

const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const updateState = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    updateState,
    subscribe,
  }
}



let initState = {
  count: 0
}
let reducer = (state, action) = > {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count+1,}case 'DECREMENT':
      return {
        ...state,
        count: state.count- 1,}default:
      return state
  }
}
let { getState, updateState, subscribe } = createStore(reducer, initState)

console.log(getState())

subscribe((a)= > {
  console.log(getState())
})

/ / since the increase
updateState({
  type: 'INCREMENT',})/ / the decrement
updateState({
  type: 'DECREMENT',})// Change it arbitrarily
updateState({
  count: 'Silly.',})Copy the code

Run the program, and the output is:

> {count: 0}

> {count: 1}

> {count: 0}

> {count: 0}
Copy the code

We can see that the printout is not {count: “stupid “}, but {count: 0}.

To make it more like a real Redux, let’s change our state manager again. We’ll change updateState to Dispatch:

const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    dispatch,
    subscribe,
  }
}
Copy the code

So far, we have implemented a planned state manager!

A state manager capable of supporting large amounts of data management

Previously, we managed a small amount of data, but now we try to increase the amount of managed data:

const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    dispatch,
    subscribe,
  }
}



let initState = {
  studyPerson: {
    name: ' '.age: ' '.like: [].sex: ' '.readedBooks: []},playfulPerson: {
    name: ' '.age: ' '.like: [].sex: ' '.readedBooks: []},books: [],}let reducer = (state, action) = > {
  switch (action.type) {
    case 'SET_STUDY_PERSON':
      return {
        ...state,
        studyPerson: action.data,
      }
    case 'SET_PLAYFUL_PERSON':
      return {
        ...state,
        playfulPerson: action.data,
      }
    case 'SET_BOOKS':
      return {
        ...state,
        books: action.data,
      }
    default:
      return state
  }
}
let { getState, dispatch, subscribe } = createStore(reducer, initState)
// Set the library owned books
dispatch({
  type: 'SET_BOOKS'.data: [{id: 1.name: 'javascript Advanced Programming '.kind: 'Computer programming'}, {id: 2.name: 'the illustration CSS 3'.kind: 'Computer programming'}, {id: 3.name: 'Javascript Functional Programming'.kind: 'Computer programming'}, {id: 4.name: Romance of The Three Kingdoms.kind: 'novel'}, {id: 5.name: 'Basketball Shooting Skills'.kind: 'Sports',}]})// Set the learner information
dispatch({
  type: 'SET_STUDY_PERSON'.data: {
    name: 'Legend of Chaos'.age: 25.like: ['Learn about computers'.'Play badminton'].sex: 'male'.readedBooks: [1.2.3,}})// Set the gamer information
dispatch({
  type: 'SET_PLAYFUL_PERSON'.data: {
    name: 'his'.age: 28.like: ['Watch variety Show'.'Reading a novel'.'Reading a magazine'].sex: 'male'.readedBooks: [4.5,}})console.log(getState())
Copy the code

From this code, we can see that with the increase of the amount of data managed, more and more logic is written in the reducer. Moreover, when dispatch is executed every time, there are too many actions sent to Dispatch, which is a little difficult to read, and we see a large pile of code.

How do we optimize this?

We can optimize it as follows:

The 1.reducerSplit into multiplereducerEach child,stateCorresponds to areducer.studyPerson,playfulPerson,booksIs the childstate.
2. Put each substateThe correspondingreducerMerge into onerootReducerAnd then therootReducerPassed to thecreateStore
3. EachactionCan be caused byactionCreaterThe generation,actionCategorize to differentjsIn the file, eachjsfileexportOut of all theactionThe correspondingactionCreater.
/ / file. / redux. Js
const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  return {
    getState,
    dispatch,
    subscribe,
  }
}
export {
  createStore,
}

/ / file. / reducers. Js
import {
  SET_STUDY_PERSON, 
  SET_PLAYFUL_PERSON, 
  SET_BOOKS,
} from './actions.js'
let studyPersonReducer = (state, action) = > {
  switch (action.type) {
    case SET_STUDY_PERSON:
      return action.data
    default:
      return state
  }
}
let playfulPersonReducer = (state, action) = > {
  switch (action.type) {
    case SET_PLAYFUL_PERSON:
      return action.data
    default:
      return state
  }
}
let booksReducer = (state, action) = > {
  switch (action.type) {
    case SET_BOOKS:
      return action.data
    default:
      return state
  }
}
export {
  studyPersonReducer,
  playfulPersonReducer,
  booksReducer,
}

/ / file. / actions. Js
export const SET_STUDY_PERSON = 'SET_STUDY_PERSON'
export const SET_PLAYFUL_PERSON = 'SET_PLAYFUL_PERSON'
export const SET_BOOKS = 'SET_BOOKS'
export const setStudyPerson = (data) = > {
  return {
    type: SET_STUDY_PERSON,
    data,
  }
}
export const setPlayfulPerson = (data) = > {
  return {
    type: SET_PLAYFUL_PERSON,
    data,
  }
}
export const setBook = (data) = > {
  return {
    type: SET_BOOKS,
    data,
  }
}

/ / file. / index. Js
import { createStore } from './redux.js'
import {
  studyPersonReducer,
  playfulPersonReducer,
  booksReducer,
} from './reducers.js'
import {
  setStudyPerson,
  setPlayfulPerson,
  setBook,
} from './actions.js'

let initState = {
  studyPerson: {
    name: ' '.age: ' '.like: [].sex: ' '.readedBooks: []},playfulPerson: {
    name: ' '.age: ' '.like: [].sex: ' '.readedBooks: []},books: [],}let rootReducer = (state, action) = > {
  return {
    studyPerson: studyPersonReducer(state.studyPerson, action),
    playfulPerson: playfulPersonReducer(state.playfulPerson, action),
    books: booksReducer(state.books, action),
  }
}
let { getState, dispatch, subscribe } = createStore(rootReducer, initState)
// Set the library owned books
dispatch(setBook([
  {
    id: 1.name: 'javascript Advanced Programming '.kind: 'Computer programming'}, {id: 2.name: 'the illustration CSS 3'.kind: 'Computer programming'}, {id: 3.name: 'Javascript Functional Programming'.kind: 'Computer programming'}, {id: 4.name: Romance of The Three Kingdoms.kind: 'novel'}, {id: 5.name: 'Basketball Shooting Skills'.kind: 'Sports',}]))// Set the learner information
dispatch(setStudyPerson({
  name: 'Legend of Chaos'.age: 25.like: ['Learn about computers'.'Play badminton'].sex: 'male'.readedBooks: [1.2.3],}))// Set the gamer information
dispatch(setPlayfulPerson({
  name: 'his'.age: 28.like: ['Watch variety Show'.'Reading a novel'.'Reading a magazine'].sex: 'male'.readedBooks: [4.5],}))console.log(getState())
Copy the code

After splitting the reducer and dividing the actions into separate JS files by class, our index.js looks a little simpler but still seems to have a lot of code. Let’s see if there’s anything else we can encapsulate?

Looking closely at the code we just implemented, the code that initialized state took up a large area, and the code that merged the reducer also seemed to be able to be extracted into tool methods

1. Let’s first extract the clutch unionreducerThe code of

The code we implemented previously as follows:

. let rootReducer =(state, action) = > {
  return {
    studyPerson: studyPersonReducer(state.studyPerson, action),
    playfulPerson: playfulPersonReducer(state.playfulPerson, action),
    books: booksReducer(state.books, action)
  }
}
...
Copy the code

It can be seen that we have defined a rootReducer function, which returns a state object. The value of each attribute of the object corresponds to a sub-reducer execution. After the reducer execution is complete, the return is the sub-state value of the attributes.

Let’s see if we can implement a reducer (combineReducers) method that only receives a reducer and returns a rootReducer, so we only need to call this method to generate a rootReducer, for example:

const rootReducer = combineReducers({
    studyPerson: studyPersonReducer,
    playfulPerson: playfulPersonReducer,
    books: booksReducer,
})
Copy the code

We try to implement the combineReducers function:

const combineReducers = (reducers) = > {
  const reducerKeys = Object.keys(reducers)
  return (state, action) = > {
    let newState = {}
    reducerKeys.forEach(key= > {
      newState[key] = reducers[key](state[key], action)
    })
    return newState
  }
}
Copy the code
2. Split and merge state

We split the reducer into sub-reducers one by one and merged them through combineReducers. But there is another problem, we still write state together, which makes the state tree very large, unintuitive, and difficult to maintain. We need to split, a state and a reducer write piece.

Let’s modify reducers.js:

/ / file. / reducers. Js
import {
  SET_STUDY_PERSON, 
  SET_PLAYFUL_PERSON, 
  SET_BOOKS 
} from './actions.js'

let initStudyPerson = {
  name: ' '.age: ' '.like: [].sex: ' '.readedBooks: [],}let studyPersonReducer = (state = initStudyPerson, action) = > {
  switch (action.type) {
    case SET_STUDY_PERSON:
      return action.data
    default:
      return state
  }
}
let initPlayfulPerson = {
  name: ' '.age: ' '.like: [].sex: ' '.readedBooks: [],}let playfulPersonReducer = (state = initPlayfulPerson, action) = > {
  switch (action.type) {
    case SET_PLAYFUL_PERSON:
      return action.data
    default:
      return state
  }
}
let booksReducer = (state = [], action) = > {
  switch (action.type) {
    case SET_BOOKS:
      return action.data
    default:
      return state
  }
}
export {
  studyPersonReducer,
  playfulPersonReducer,
  booksReducer,
}
Copy the code

Add dispatch({type: Symbol()}) to createStore, and place the combineReducers method in the redux.js file

/ / file. / redux. Js
const createStore = (reducer, initState) = > {
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  Reducer does not match any type. Go to the default logic and return the initial state value.
  dispatch({ type: Symbol()})return {
    getState,
    dispatch,
    subscribe,
  }
}
const combineReducers = (reducers) = > {
  const reducerKeys = Object.keys(reducers)
  return (state, action) = > {
    let newState = {}
    reducerKeys.forEach(key= > {
      newState[key] = reducers[key](state[key], action)
    })
    return newState
  }
}
export {
  createStore,
  combineReducers,
}
Copy the code

See Demo-1 for the complete source code for this section

At this point, our Redux implementation is pretty much complete!

Middleware middleware

What is Redux middleware?

Redux’s middleware can be understood as a wrapper around the Dispatch method, enhancing the functionality of Dispatch.

1. Try wrapping the underdispatch

Suppose we need to print the state before and after redux and the action that triggers the state change each time redux changes state.

We can temporarily package the dispatch like this:

import { createStore, combineReducers } from './redux.js'

const rootReducer = combineReducers({
    count: countReducer
})

const store = createStore(rootReducer);

const next = store.dispatch;

store.dispatch = (action) = > {
  console.log('previous state:', store.getState())
  next(action)
  console.log('next state:', store.getState())
  console.log('action:', action)
}

store.dispatch({
  type: 'INCREMENT'
});
Copy the code

Let’s run the code. The output is:

> previous state: {count: 0}

> next state: {count: 0}

> action: {type: "INCREMENT"}
Copy the code

As you can see from the output, we have implemented the dispatch enhancement. When the state is changed, the state value before the state is changed, the value after the state is changed, and the action triggering the state change are correctly printed.

2. Multi-middleware packagedispatch

We implemented an enhanced Dispatch requirement above, and now we have a new enhanced requirement on top of the original requirement. For example: I want to print the current time every time state changes.

Try implementing the following code:

const store = createStore(rootReducer);

const next = store.dispatch;

store.dispatch = (action) = > {
  console.log('state change time:'.new Date().toLocaleString())
  console.log('previous state:', store.getState())
  next(action)
  console.log('next state:', store.getState())
  console.log('action:', action)
}
Copy the code

If we now have a new enhancement requirement, and want to record the cause of the error every time we change state, let’s change the enhancement code again:

const store = createStore(rootReducer);

const next = store.dispatch;

store.dispatch = (action) = > {
  try {
    console.log('state change time:'.new Date().toLocaleString())
    console.log('previous state:', store.getState())
    next(action)
    console.log('next state:', store.getState())
    console.log('action:', action)
  } catch (e) {
    console.log('Error message:', e)
  }
}
Copy the code

If there are new requirements for 叒 Yi…

Should the code be enhanced all the time? It’s a hassle, and the Dispatch function will get bigger and bigger and harder to maintain. So this approach is not desirable, we need to consider how to implement a more scalable multi-middleware wrapper dispatch method.

  1. Separate the different enhancement requirements into a single function:
const store = createStore(rootReducer);

const next = store.dispatch;

const loggerMiddleware = (action) = > {
  console.log('previous state:', store.getState())
  next(action)
  console.log('next state:', store.getState())
  console.log('action:', action)
}

const updateTimeMiddleware = (action) = > {
  console.log('state change time:'.new Date().toLocaleString())
  loggerMiddleware(action)
}

const exceptionMiddleware = (action) = > {
  try {
    updateTimeMiddleware(action)
  } catch (e) {
    console.log('Error message:', e)
  }
}

store.dispatch = exceptionMiddleware
Copy the code

In this way, it seems that the various enhancement requirements are separated into a single function, but neither function is pure and has dependencies on external data, which is not conducive to scaling.

Let’s make these enhancements pure.

We can transform the enhancement function in two steps: (1) Extract the enhancement function called in the enhancement function and pass it into the enhancement function as parameters:

const store = createStore(rootReducer);

const next = store.dispatch;

const loggerMiddleware = (next) = > (action) => {
  console.log('previous state:', store.getState())
  next(action)
  console.log('next state:', store.getState())
  console.log('action:', action)
}

const updateTimeMiddleware = (next) = > (action) => {
  console.log('state change time:'.new Date().toLocaleString())
  next(action)
}

const exceptionMiddleware = (next) = > (action) => {
  try {
    next(action)
  } catch (e) {
    console.log('Error message:', e)
  }
}

store.dispatch = exceptionMiddleware(updateTimeMiddleware(loggerMiddleware(next)))
Copy the code

(2) Remove the call to store from the enhancement function and pass it as an argument to the enhancement function:

const store = createStore(rootReducer);

const next = store.dispatch;

const loggerMiddleware = (store) = > (next) => (action) = > {
  console.log('previous state:', store.getState())
  next(action)
  console.log('next state:', store.getState())
  console.log('action:', action)
}

const updateTimeMiddleware = (store) = > (next) => (action) = > {
  console.log('state change time:'.new Date().toLocaleString())
  next(action)
}

const exceptionMiddleware = (store) = > (next) => (action) = > {
  try {
    next(action)
  } catch (e) {
    console.log('Error message:', e)
  }
}

const logger = loggerMiddleware(store)
const updateTime = updateTimeMiddleware(store)
const exception = exceptionMiddleware(store)

store.dispatch = exception(updateTime(logger(next)))
Copy the code

At this point, we’ve really implemented extensible, stand-alone, pure enhancements (enhancements are redux middleware) that can be easily combined.

3. Optimize the use of middleware

We’ve implemented scalable, standalone, pure middleware above, but we need to implement it eventually

const logger = loggerMiddleware(store)
const updateTime = updateTimeMiddleware(store)
const exception = exceptionMiddleware(store)
Copy the code

To generate Logger, updateTime, exception, and execute

store.dispatch = exception(updateTime(logger(next)))
Copy the code

To override the Store’s dispatch method.

We don’t need to care how the middleware is used, we just need to know that there are three middleware, and the rest of the details are encapsulated.

For example, we can wrap the createStore to encapsulate the details of middleware usage in the code that wraps the createStore.

Expected usage:

/** * applyMiddleware receives middleware, wrapping middleware usage details inside applyMiddleware, and returns a function that receives the old createStore and returns the new createStore */
const newCreateStore = applyMiddleware(exceptionMiddleware, updateTimeMiddleware, loggerMiddleware)(createStore);

// newCreateStore returns a store whose Dispatch has been overwritten
const store = newCreateStore(reducer);
Copy the code

Implement applyMiddleware:

const applyMiddleware = (. middlewares) = > (oldCreateStore) => (reducer, initState) = > {
  / / store
  let store = oldCreateStore(reducer, initState)
  LoggerMiddleware (store); // Pass each middleware to store.
  To prevent middleware from modifying other methods of store, we only expose store's getState method
  Chain = [logger, updateTime, exception]
  let chain = middlewares.map(middleware= > middleware({ getState: store.getState }))
  // Get the store dispatch method
  let next = store.dispatch
  / / implementation exception (updateTime (logger (next)))
  chain.reverse().map(middleware= > {
    next = middleware(next)
  })
  // Replace store's dispatch method
  store.dispatch = next
  // Return the new store
  return store
}
Copy the code

We now wrap the createStore code outside the createStore function. For ease of use, we will modify the createStore function slightly.

const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, updateTimeMiddleware, loggerMiddleware)

const store = createStore(reducer, initState, rewriteCreateStoreFunc)
Copy the code

The modified createStore code is as follows:

const createStore = (reducer, initState, rewriteCreateStoreFunc) = > {
  // If there is rewriteCreateStoreFunc, a new createStore is generated
  if(rewriteCreateStoreFunc){
      const newCreateStore =  rewriteCreateStoreFunc(createStore);
      return newCreateStore(reducer, initState);
  }
  

  // The following is the old createStore logic
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
  }

  Reducer does not match any type. Go to the default logic and return the initial state value.
  dispatch({ type: Symbol()})return {
    getState,
    dispatch,
    subscribe,
  }
}
Copy the code

See Demo -2 for the complete source code for this section

At this point, our Redux middleware functionality is fully implemented.

Redux other features

unsubscribe

Store. Subscribe is used to subscribe to the state change method, since there is a subscription, then there is a need to unsubscribe, let’s implement the unsubscribe function.

Modify the store. Subscribe implementation as follows:

const subscribe = (listener) = > {
  listeners.push(listener)
  return (a)= > {
    const index = listeners.indexOf(listener)
    listeners.splice(index, 1)}}Copy the code

The usage method is as follows:

/ / subscribe
const unsubscribe = store.subscribe((a)= > {
  let state = store.getState();
  console.log(state);
});
/ / unsubscribe
unsubscribe();
Copy the code
The createStore function can omit the initState argument

CreateStore allows omitting the initState argument, for example:

let store = createStore(rootReducer, rewriteCreateStoreFunc)
Copy the code

Modify our createStore method to omit the initState argument:

const createStore = (reducer, initState, rewriteCreateStoreFunc) = > {
  // Compatible with the omitted parameter initState
  if (typeof initState === 'function') {
    rewriteCreateStoreFunc = initState
    initState = undefined
  }

  // If there is rewriteCreateStoreFunc, a new createStore is generated
  if(rewriteCreateStoreFunc){
      const newCreateStore =  rewriteCreateStoreFunc(createStore);
      return newCreateStore(reducer, initState);
  }
  

  // The following is the old createStore logic
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
    return (a)= > {
      const index = listeners.indexOf(listener)
      listeners.splice(index, 1)}}Reducer does not match any type. Go to the default logic and return the initial state value.
  dispatch({ type: Symbol()})return {
    getState,
    dispatch,
    subscribe,
  }
}
Copy the code
ReplaceReducer method

Sometimes, our pages have been loaded on-demand. If we want to load the reducer corresponding to the components of each on-demand page as needed, we can use replaceReducer to achieve this, for example:

Reducer the initial page needs
const reducer = combineReducers({
  books: booksReducer
});
const store = createStore(reducer);

Reducer 1 Need to load the reducer when the page is loaded
// Then, generate a new reducer using combineReducers
// Finally, replace the reducer with the new reducer using the store.replaceReducer method
const nextReducer = combineReducers({
  playfulPerson: playfulPersonReducer,
  books: booksReducer,
});
store.replaceReducer(nextReducer);

// If the reducer is needed, load the reducer when the page is loaded
// Then, generate a new reducer using combineReducers
// Finally, replace the reducer with the new reducer using the store.replaceReducer method
const nextReducer = combineReducers({
  studyPerson: studyPersonReducer,
  playfulPerson: playfulPersonReducer,
  books: booksReducer,
});
store.replaceReducer(nextReducer);
Copy the code

The replaceReducer method is added to the createStore function, and the store returned contains the replaceReducer method. The code is implemented as follows:

const createStore = (reducer, initState, rewriteCreateStoreFunc) = > {
  // Compatible with the omitted parameter initState
  if (typeof initState === 'function') {
    rewriteCreateStoreFunc = initState
    initState = undefined
  }

  // If there is rewriteCreateStoreFunc, a new createStore is generated
  if(rewriteCreateStoreFunc){
      const newCreateStore =  rewriteCreateStoreFunc(createStore);
      return newCreateStore(reducer, initState);
  }
  

  // The following is the old createStore logic
  let listeners = []
  let state = initState || {}

  const getState = (a)= > state

  const dispatch = (action) = > {
    state = reducer(state, action)
    listeners.forEach(listerner= > listerner())
  }

  const subscribe = (listener) = > {
    listeners.push(listener)
    return (a)= > {
      const index = listeners.indexOf(listener)
      listeners.splice(index, 1)}}function replaceReducer(nextReducer) {
    reducer = nextReducer
    // Update the new Reducer default state to the state tree
    dispatch({ type: Symbol()})}Reducer does not match any type. Go to the default logic and return the initial state value.
  dispatch({ type: Symbol()})return {
    getState,
    dispatch,
    subscribe,
    replaceReducer,
  }
}
Copy the code

So far, the Redux I want to implement has been fully implemented, but the explanation and implementation of the bindActionCreators method, which is less widely used, have been omitted to shorten the scope, and various verification codes have been removed from the implementation code.

See Demo -3 for the complete sample source code

The last

Pure functions are mentioned, so what is a pure function?

In simple terms, the same input always produces the same output without any side effects.

Pure functions are a concept in functional programming.

To recommend a JS functional programming guide book, support online browsing functional programming guide