What is the story?

Manage all the state data of the whole front-end project (single-page application), uniformly store the state of the whole application in one place (Store), save it into a state tree, and dispatch an action to notify store of modification of data. A component modifies its state by subscribing to a modification event to get the latest data.

The three principles

  1. The state of the entire application is stored in the Store, and there is only one store.
  2. State in a store is read-only, and the only way to change state is to dispatch an action.
  3. A reducer function modifies state and returns a new state each time. The original object cannot be modified directly.

Why use Redux(Application Scenario)

  1. The complexity of single-page applications makes it difficult to manage changing states.
  2. Non-parent-child component communication.
  3. Public state for all pages.

From the source code, the entire redux source code, a total of 5 functions, and a __DO_NOT_USE__ActionTypes(default action, all action types cannot be repeated, will be detailed below this point), so the next to the details of the 5 functions.

1.createStore(reducer, preloadedState, enhancer)

To use Redux, the first step is to create a global store(of course, a unique store) by calling this function (createStore). This function takes three arguments. A store holds all of your data, so you can think of it as a container.

Pass the reducer and initState to create a store.

Store returns keys, modifiers, phones.

If you need to modify the data, you have to go through the modifier. If you need to know when the data has been modified, you can call the Store and tell it that the data has been modified and tell me.

Let’s start with the function it returns:

getState() => currentState

Returns the current store state tree, containing all states. Here is a question when reading the source code.

read-only

With this question in mind, I gave Redux a PR. I got a reply:

Yes – getState() just returns whatever the current state value is. It’s up to you to avoid accidentally mutating it.

Yes, getState() returns only the current state value. You want to avoid messing it up by accident.

That said, it’s important to note that the value returned by getState() can’t be changed directly, or you’ll be in the deep end

subscribe(listener) => unsubscribe()

A function is passed to listen for changes in the store, and once the store changes, all listener functions are looped through. Calling this function also returns a function that cancels listening. Call the returned function, then cancel the listener.

dispatch(action) => action

Dispatch receives an action. Inside the function all the reducer is executed and the current state and actions are passed into the reducer, and then all the listening functions are executed. From the source code cut out a paragraph:

const dispatch = (action) = > {
	currentState = currentReducer(currentState, action);
	
	const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    
    return action
}
Copy the code
action

An object must have a type that represents the action to take place. Other attributes can be set freely. There is a specification in the community that puts all other data into the payload.

	function login(){
		retrun {
			type: 'LOGIN'.payload: {
				username: 'xxx'.passworld: 'xxxx'}}}Copy the code

replaceReducer(nextReducer)

A new reducer is passed to REPLACE the previous reducer and an action with ActionType REPLACE is sent.

Let’s look at the three arguments it receives

reducer

A pure function that takes the current state and action as arguments and returns the new state.

When the Store receives the Action, it calls the Reducer and must present a new State so that the data in the Store will change.

Write a simple Reducer

const reducer = function (state, action) {
	switch(action.type){
		case 'TYPE':
  			return{... state, newState}default:
  			returnstate; }};Copy the code

preloadedState

The parameter in the source code is called this, actually I prefer to call it initState, the initialization state, why not call the source code initState? Because it is not correct, the source code will send dispatch() with type init by default, and then go to the reducer (see the reducer code above). State If the reducer is set to a default value, for example:

const reducer = function (state = {list: []}, action) {
	switch(action.type){
		case 'TYPE':
  			return{... state, newState}default:
  			returnstate; }};Copy the code

{list: []} is returned by default, giving a true initState.

enhancer

Designed to support rapid creation of middleware.


The __DO_NOT_USE__ActionTypes mentioned above are two actionTypes:

  • @@redux/INIT: used to send a default dispatch internally
  • @@redux/REPLACE: Replaces the reducer

2.bindActionCreators(actionCreators, dispatch) => boundActionCreators

All generated action functions are iterated and executed, returning functions wrapped by Dispatch that can be called directly to dispatch a request.

actionCreators

This argument is an object containing the function that generates the action, for example:

const actionCreators = {
	login: (a)= > {
		return {
			type: 'LOGIN'.payload: {
				username: 'xxx'.passworld: 'xxxx'}}},logout: (a)= > {
		retrun {
			type: 'LOGOUT'}}}Copy the code

If a function is passed in, the function is executed to get action, which returns a Dispatch (action).

dispatch

This is the dispatch returned by createStore


3.combineReducers(reducers)

In the project development, we need to write reducer modules and merge multiple Reducer modules with this function. Pass in a Reducer collection.

a.js
export (state = {list: []}, action) => {
	switch (action.type) {
	    case "LIST_CHANGE":
	        return{... state, ... action.payload };default:
	        return state;
	}
}

b.js
export (state = {list: []}, action) => {
	switch (action.type) {
	    case "LIST":
	        return{... state, ... action.payload };default:
	        return state;
	}
}

combineReducers.js
import a from './a';
import b from './b';

combineReducers({
	a: a,
	b: b
})
Copy the code

Both A and B have the state list, but they are not related. To use them separately, combineReducers should be used to merge them.

Here’s a simple implementation of this function:

function combineReducers(reducers) {
    return (state = {}, action) = > {
        return Object.keys(reducers).reduce((currentState, key) = > {
            currentState[key] = reducers[key](state[key], action);
            returncurrentState; }, {}}; }Copy the code

4.compose

It can be said that js function formula is very important method, a bunch of functions in series to execute, execute from right to left (that is, reverse order), function parameters are the result of the previous function. Take a look at an example:

const fn1 = (val) = > val + 'fn1';

const fn2 = (val) = > val + 'fn2';

const fn3 = (val) = > val + 'fn3';

const dispatch = compose(fn1, fn2, fn3);

console.log(dispatch('test'));
Copy the code

The final result is testfn3fn2fn1

Test is passed to fn3 when the argument, fn3’s return value is given to fn2….

compose.js

function compose(. fns){
    if(fns.length==1) return fns[0];
    return fns.reduce((a,b) = >(. args) = >a(b(... args))); }Copy the code

5.applyMiddleware

This function is used to add middleware, which redux implements by adapting dispatches, to do other things while modifying data.

Using middleware

const middleware = applyMiddleware(logger);
Copy the code

Middleware functions need to be passed in, which can be multiple functions, and compose is used in applyMiddleware, so the functions are executed from right to left.

const createStoreWithMiddleware = middleware(createStore);
Copy the code

Because the middleware is implemented by adapting dispatches, you need to pass in the method to create a store.

const store = createStoreWithMiddleware(reducer, preloadedState)
Copy the code

We pass in the parameters that the createStore needs to receive and return the Store object.

Implement a Logger middleware

const logger = function({dispatch, getState}){
   return function(next){
      return function(action){
	        console.log('oldState',getState());
	        next(action); // Send the actual action
	        console.log('newState',getState()); }}}Copy the code

First middleware passes in unmodified Dispatches and getState, where next is the equivalent of dispatch, to deliver an actual modification action.

Source code paste:

export default function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)const middlewareAPI = {
      getState: store.getState,
      dispatch: (. args) = >dispatch(... args) }const chain = middlewares.map(middleware= >middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch)return {
      ...store,
      dispatch
    }
  }
}
Copy the code

The middlewareAPI is a method for storing unmodified components. As mentioned above, for compose, the first parameter is passed in to a dispatch, and a modified dispatch is returned via the compose function, which will be executed in sequence.

The last

Thank you for reading my article