One, foreword

Why redux-Actions?

When I first met redux, I took over the company’s original projects and found that the previous boss introduced redux when dealing with it.

Discovery does make dealing with Redux a lot easier, and this is no exception when I try to read the source code and write articles to better use a component or plug-in.

The findings are also interesting and I recommend bringing in redux-Actions when using Redux

Here’s how to use it and implement a simple Redux-Actions by hand

Second, the introduction

When learning redux, I always felt that the action and Reducer code was too dull, for example

2.1 create action

let increment = () = >({type:"increment"})
Copy the code

2.2 reducer

let reducer = (state,action) = >{
    switch(action.type){
      case "increment":return {count:state.count+1};break;
      case "decrement":return {count:state.count-1};break;
      default:returnstate; }}Copy the code

2.3 the trigger action

dispatch(increment())
Copy the code

In summary, we inevitably think that a small demo made by increment and reducer is ok, but when the logic is complicated, the project management and maintenance will show drawbacks. Therefore, the final approach is to separate them and give developers more initiative in the reducer instead of just increasing or decreasing numbers.

Redux-actions Main functions include createAction, createActions, handleAction, handleActions, and combineActions.

So basically you only have createAction, handleActions, handleAction

So we’re just going to talk about these three.

CreateAction ()

3.1 usage

In general, you can create an Action by:

let increment = () = >({type:"increment"})
let incrementObj = increment();// { type:"increment"}
Copy the code

Create an action with createAction

import { createAction } from 'redux-actions';
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
let objincrement = increment(10);// {type:"increment",paylaod:10}
Copy the code

We can see that

let increment = () = >({type:"increment"})
let incrementObj = increment();// { type:"increment"}
Copy the code

with

const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
Copy the code

It’s equivalent, so why not just do it the old-fashioned way?

It is not hard to find two things:

  1. Traditionally, you write a function to return incrementObj, but with createAtion wrapped you don’t have to write your own function
  2. The traditional way is to return incrementObj if there is a payload that needs to be added to it. But with increment returned by createAction, we add payload, so it’s very easy to pass in a parameter, and it takes it as the payload.
let increment = () = >({type:"increment"Content:123})
Copy the code

3.2 Principle Implementation

Let’s start with a simple value passed in as the type parameter, which implements the following code

const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
Copy the code

We find that createAction(‘increment’)() returns the final action object. Isn’t that just a Currie function?

So we can write very simply, as shown in the code below, that we treat type as a property of the action object

function createAction(type) {
    return () = > {
        const action = {
            type
        };
        return action;
    };
}
Copy the code

Okay, so now, let’s implement this function, which is payload

const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
Copy the code

Obviously, this payload is the parameter returned by createAction(‘increment’), so we can add payload to the action as easily as possible.

function createAction(type) {
    return (payload) = > {
        const action = {
            type,
            payload
        };
        return action;
    };
}
Copy the code

But in the first case, we don’t want to send payload, so we don’t want to send any action back with payload, but we’re going to write it like this so that we have to send payload by default.

So we need to add a check, so when we don’t send payload, we don’t add payload to the action.

function createAction(type) {
    return (payload) = > {
        const action = {
            type,
        };
        if(payload ! = =undefined){
            action.payload = payload
        }
        return action;
    };
}
Copy the code

I prefer the following in real projects, but it is equivalent to the above

function createAction(type) {
    return (payload) = > {
        constaction = { type, ... payload? {payload}:{} };return action;
    };
}
Copy the code

You can also pass a callback to createAction that handles payload.

const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
Copy the code

As shown in the code above, we want the payload in the action returned after passing in 10 to be a multiple of 2

const increment = createAction('increment'.(t) = > t * 2);
let objincrement = increment(10);// {type:"increment",paylaod:20}
Copy the code

Now, let’s do that.

function createAction(type,payloadCreator) {
    return (payload) = > {
        const action = {
            type,
        };
        if(payload ! = =undefined){
            action.payload = payloadCreator(payload)
        }
        return action;
    };
}
Copy the code

Oh, my God, that’s so easy! But we made the same mistake as before: when we use createAction, we don’t necessarily pass payloadCreator as a callback, so we need to check

function createAction(type,payloadCreator) {
    return (payload) = > {
        const action = {
            type,
        };
        if(payload ! = =undefined){ action.payload = payloadCreator? payloadCreator(payload):payload }return action;
    };
}
Copy the code

Oh, my God. Perfect.

Next take a look at the handleActions for redux-Action

Get to know handleActions

Let’s first look at how traditional Reducer is used

let reducer = (state,action) = >{
    switch(action.type){
      case "increment":return {count:state.count+1};break;
      case "decrement":return {count:state.count-1};break;
      default:returnstate; }}Copy the code

Now look at the handleActions

const INCREMENT = "increment"
const DECREMENT = "decrement"
var reducer = handleActions({
    [INCREMENT]: (state, action) = > ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) = > ({
      counter: state.counter - action.payload
    })
},initstate)
Copy the code

Don’t be alarmed by {[DECREMENT]:(){}}, which means properties are written as variables.

Let’s look at the results on the console console.log(reducer)

The result is a reducer function.

This achieves the freedom of function in reducer. What program do we want to write

{[increment]:(state,action) = >{}} 
Copy the code

You can do it within this function, but you can also separate these functions into a file and import them

import {increment,decrement}from "./reducers.js"
var initstate = {count:0}
var reducer = createReducer({
    [INCREMENT]: increment,
    [DECREMENT]: decrement
},initstate)
Copy the code

reducers.js

//reducers.js
export let increment = (state,action) = >({counter: state.counter + action.payload})
export let decrement = (state,action) = >({counter: state.counter - action.payload})
Copy the code

Visible,

Handleactions can simplify reducers writing without so many switches and separate functions so that reducer no longer has a lot of code.

I was going to do the implementation of handleActions, but before we do that, we have to talk about handleActions, yeah, look carefully, there’s no S

Five, understanding and handwriting handleAction

5.1 usage

Let’s see how it works

const incrementReducer = handleAction(INCREMENT, (state, action) = > {
  return {counter: state.counter + action.payload}
}, initialState);

Copy the code

The difference with handleActions is that the Reducer generated by handleActions is dedicated to one action.

5.2 Principle Implementation

If you have read redux, you should know that reducer(State,action) returns a new state, The new state is then compared to the old state, and if the two are found to be different, the component using state is re-rendered and the new state is assigned to the old state.

That is, handleAction() returns a reducer function, followed by incrementReducer() returns a new state.

The first implementation returns a reducer function

function handleAction(type, callback) {
    return (state, action) = >{}; }Copy the code

The next step should be to reducer(state,action) it is time to return state, which is what is returned below

(state, action) => {
      
};
Copy the code

Callback (state) and return a new state

function handleAction(type, callback) {
    return (state, action) = > {
        
      return callback(state)
    };
}

Copy the code

And you might wonder why you want to do it this way, instead of just doing something like this, which is missing a layer of inclusion.

function handleAction(state,type, callback) {
    return callback(state)
}
Copy the code

That’s the genius of it. Callback (state) may or may not be executed when the reducer() returned by handleAction() is reducer(). It will only be executed if the type sent by handleAction matches the action.type sent by reducer(). Otherwise, return state directly. Indicates that no processing is performed

function handleAction(type, callback) {
    return (state, action) = > {
        
      return callback(state)
    };
}
Copy the code

So we need another layer of judgment

function handleAction(type, callback) {
    return (state, action) = > {
        if(action.type ! == type) {return state;
        }
        return callback(state)
    };
}
Copy the code

How perfect!

Ok, now let’s implement handleActions

Six, handleActions principle implementation

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type= > {
        return handleAction(type, handlers[type]);
    });
    constreducer = reduceReducers(... reducers)return (state = defaultState, action) = > reducer(state, action)
}
Copy the code

Look, just a few lines of code, it’s pretty simple, but it’s probably hard to understand, but that’s okay, I’m still going to make it crass.

Let’s take the example we used above

var reducer = handleActions({
    [INCREMENT]: (state, action) = > ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) = > ({
      counter: state.counter - action.payload
    })
},initstate)
Copy the code
{
    [INCREMENT]: (state, action) = > ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) = > ({
      counter: state.counter-action.payload})}Copy the code

The object above, after the following code

const reducers = Object.keys(handlers).map(type= > {
        return handleAction(type, handlers[type]);
    });
Copy the code

The reducer returned is, in fact

[
  handleAction(INCREMENT,(state, action) = > ({
      counter: state.counter + action.payload
  })),
  handleAction(DECREMENT,(state, action) = > ({
      counter: state.counter + action.payload
  })),
]
Copy the code

Why should it be an array of handleActions,

I want to iterate through all the handleActions in this array every time I dispatch(action).

Isn’t the reducer returned by each handleAction executed? True, but remember what we said above, if handleAction determines type and action.type it does not handle state and returns state

function handleAction(type, callback) {
    return (state, action) = > {
        if(action.type ! == type) {return state;
        }
        return callback(state)
    };
}
Copy the code

No it doesn’t matter if every handleAction is executed

So how do you iterate, map, forEach? No, none of that is true. Let’s go back to the source code

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type= > {
        return handleAction(type, handlers[type]);
    });
    constreducer = reduceReducers(... reducers)return (state = defaultState, action) = > reducer(state, action)
}
Copy the code

Using the

constreducer = reduceReducers(... reducers)Copy the code

The method of reduceReducers is used. As the name implies, look at the name of this method, which means to iterate and execute the array of reducers by using reduce. Which is this array right here.

[
  handleAction(INCREMENT,(state, action) = > ({
      counter: state.counter + action.payload
  })),
  handleAction(DECREMENT,(state, action) = > ({
      counter: state.counter + action.payload
  })),
]
Copy the code

Let’s take a look at the internal principle of reduceReducers

function reduceReducers(. args) {
    const reducers = args;
    return (prevState, value) = > {
        return reducers.reduce((newState, reducer, index) = > {
            return reducer(newState, value);
        }, prevState);
    };
};
Copy the code

We found that putting the reducers array into the reduceReducers and then executing the reduceReducers will return

(prevState, value) => {
    return reducers.reduce((newState, reducer, index) = > {
        return reducer(newState, value);
    }, prevState);
};
Copy the code

This method, that is to say executing this method will execute

return reducers.reduce((newState, reducer, index) = > {
        return reducer(newState, value);
    }, prevState);
Copy the code

In other words, reducers can be performed using Reduce traversal. Why should reduce traversal be used?

This is because the state returned by the previous handleAction needs to be passed to the next one.

For example, the compose function is composed by writing the compose method by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function by writing the compose function.

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type= > {
        return handleAction(type, handlers[type]);
    });
    constreducer = reduceReducers(... reducers)return (state = defaultState, action) = > reducer(state, action)
}
Copy the code

Reducer is reduceReducers(… Reducers), that is

reducer = (prevState, value) = > {
    return reducers.reduce((newState, reducer, index) = > {
        return reducer(newState, value);
    }, prevState);
};
Copy the code

While handleActions return

(state = defaultState, action) => reducer(state, action)
Copy the code

In other words, handleActions actually returns a method like this.

(state = defaultState, action) => {
    return reducers.reduce((newState, reducer, index) = > {
        return reducer(newState, value);
    }, state);
}
Copy the code

Boy, using reduce to pass state between handleActions is a good way to do it. Learned.

Github redux-Actions is a simplified version of redux-actions: github.com/redux-utili…

The last

This article was first published on the public account “Front-end Sunshine”, welcome to join the technical exchange group.

Refer to the article

  • The React series – the FSA knowledge segmentfault.com/a/119000001 】…
  • The use of the story – the actions 】 【 zhuanlan.zhihu.com/p/273569290
  • 【 Knowledge of front-end progression and Handwriting compose method 】
  • Redux source code parsing series (a) — Redux implementation ideas)