Analysis of Redux Principle

Posted by plouka on Tue, 23 Jul 2019 12:07:59 +0200

What is Redux

Many people think that Redux must be used in conjunction with React. In fact, it is not. Redux is a JavaScript state container. As long as you use state in your project and the state is very complex, you can use Redux to manage your project state. It can be used in react or in Vue, of course. Other frameworks apply.

I. The Working Principle of Reux

Picture above (Pictures from the Internet)

  1. First we find the top state.
  2. In react, state determines the view (ui), and the change of state calls render () method of React to change the view.
  3. Users distribute an action like reducer through events such as clicking a button and moving a mouse.
  4. When reducer receives action, it updates the state
  5. store is a collection of all states, which can be regarded as a collection of all states.

Of course, I can't understand what this is talking about now, but when I finish reading this article and come back to this picture and this passage, I will have a feeling of sudden understanding.

1.action

Action is essentially an object. It must have a key named type, such as {type:'add'}, and {type:'add'} is an action.
But instead of using action directly in our actual work, we use action to create functions. (Don't get confused)
As the name implies, an action creation function is a function whose function is to return an action, such as:

function add() {
    return {type: 'add'}
}

2.reducer

reducer is actually a function that receives two parameters, the first is the state to be managed, and the second is action. reducer operates on state differently based on the type value of the action passed in, and then returns a new state instead of modifying it on the basis of the original state, but if it encounters an unknown (mismatched) action, it returns to the original state without any change.

function reducer(state = {money: 0}, action) {
    //Returning a new state can use the Object.assign() method provided by es6, or the extension operator (this method requires babel-preset-state-3 support)
    switch (action.type) {
        case '+':
            return Object.assign({}, state, {money: state.money + 1});
        case '-':
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

3.store

You can think of store as a state tree that contains all the states of the entire redeux application.
We use the createStore method provided by redux to generate the store

import {createStore} from 'redux';
const store = createStore(reducer);

store provides several methods for us to use. Here are three of our commonly used methods:

store.getState();//Get the whole state tree
store.dispatch();//The only way to change state
store.subscribe();//Subscribe to a function that is called whenever the state changes

Next, I'll show you a complete application of redux and show you how to use these three methods.

import {createStore} from 'redux';

//Give the initial state a default: {money: 0}
function reducer(state = {money: 0}, action) {
    //Returning a new state can use the Object.assign() method provided by es6, or the extension operator (this method requires babel-preset-state-3 support)
    switch (action.type) {
        case '+':
            return Object.assign({}, state, {money: state.money + 1});
        case '-':
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//Action creates a function that returns an action
function add() {
    return {type: '+'}
}

function subtraction() {
    return {type: '-'}
}

//Creating a single state tree
const store = createStore(reducer);

console.log(store.getState());//{money: 0}, initial state, no change (get the current state through getState)

//Store changes the store by dispatch and passing in action as a parameter
store.dispatch(add());
console.log(store.getState());//{money: 1}, reducer receives the'+'command and finds a dollar.

store.dispatch(subtraction());
console.log(store.getState());//{money: 0}, reducer receives the'-'command and loses another dollar.

store.dispatch({type:'I'm here to make trouble.'});
console.log(store.getState());//{money: 0}, reducer receives an unidentified command and returns to the original state

At this time, we will find several problems:

  1. We need console.log() to know the changed state every time the state changes.
  2. The type of action is actually a string. If we need to maintain the project and change the value of the type, we need to modify it in many places, which becomes very troublesome.

At this point, we can use store.subscribe() to subscribe to an event instead of console.log() after each dispatch to know the changed state.

function listen() {
    console.log(store.getState());
}

store.subscribe(listen);

Maintain the type as a constant, so that we only need to maintain the constant in the future maintenance process, we currently use the demo to type places too few may not feel, but in the actual project this method is very practical.

const ADD = '+', SUBTRACTION = '-';

Our optimized code is as follows:

import {createStore} from 'redux';

//Define constants for easy maintenance
const ADD = '+', SUBTRACTION = '-';

//Give the initial state a default: {money: 0}
function reducer(state = {money: 0}, action) {
    //Returning a new state can use the Object.assign() method provided by es6, or the extension operator (this method requires babel-preset-state-3 support)
    switch (action.type) {
        case ADD:
            return Object.assign({}, state, {money: state.money + 1});
        case SUBTRACTION:
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//Action creates a function that returns an action
function add() {
    return {type: ADD}
}

function subtraction() {
    return {type: SUBTRACTION}
}

//Changed state of printing
function listen() {
    console.log(store.getState());
}

//Creating a single state tree
const store = createStore(reducer);

//Subscribe to listen and execute listen after each dispatch to print the status (only after dispatch is executed, and not when the status is initialized)
store.subscribe(listen);

console.log(store.getState());//Initial state, no change

//Store changes the store by dispatch and passing in action as a parameter
store.dispatch(add());
store.dispatch(subtraction());
store.dispatch({type: 'I'm here to make trouble.'});

/*The printing results of the console are as follows:
{money: 0}
{money: 1}
{money: 0}
{money: 0}*/

Supplement:
An application can only have one store, and then there is a problem. If there are multiple reducers to handle different states, and the createStore can accept a reducer, then we need the combineReducers method provided by redux to combine multiple reducers into a reducer.

import {combineReducers} from 'redux';

const reducerFamily=combineReducers({
    reduceSon,
    reduceDaughter,
    reducerFather,
    reducerMother
})
const store = createStore(reducerFamily);

2. Using redux in React

If you can react, you must also know the official scaffolding tool creact-react-app. First, create a project using creact-react-app, then delete all files in the src directory, and then you can happily tap the code.

Create three files under src
index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
//Introduce our reducer and action to create functions
import {reducer, add, subtraction} from './index.redux'
import App from './App'

//Create a store
const store = createStore(reducer);

//The store.subscribe method accepts a function as a parameter.
// So write the ReactDOM.render method in a function
function listen() {
    //Pass the store and action creation functions to the sub-component App in the form of attributes, respectively
    ReactDOM.render(<App store={store} add={add} subtraction={subtraction}/>,
        document.querySelector('#root'));
}

//Because there is no dispatch operation to change the store just entering the page.
// So listen won't execute. We need to call it manually once.
listen();

//The point is that if you change the store, the page will be re-rendered.
// Try not to write this line of code will be how it works
store.subscribe(listen);

App.js

import React from 'react'

export default class App extends React.Component {
    render() {
        //Get store from attribute, action creates function
        const {store, add, subtraction} = this.props;
        //Getting state
        let state = store.getState();
        return <div>
            <h1>I have{state.money}element</h1>

            {/*Change the store through the store.dispatch method, and the page will change as well.*/}
            <button onClick={() => {store.dispatch(add())}}>
                //Pick up a dollar
            </button>

            <button onClick={() => {store.dispatch(subtraction())}}>
                //One dollar was lost.
            </button>
        </div>
    }
}

index.redux.js

//Define constants for easy maintenance
const ADD = '+', SUBTRACTION = '-';

//Give the initial state a default: {money: 0}
export function reducer(state = {money: 0}, action) {
    //Returning a new state can use the Object.assign() method provided by es6, or the extension operator (this method requires babel-preset-state-3 support)
    switch (action.type) {
        case ADD:
            return Object.assign({}, state, {money: state.money + 1});
        case SUBTRACTION:
            return {...state, ...{money: state.money - 1}};
        default:
            return state;
    }
}

//Action creates a function that returns an action
export function add() {
    return {type: ADD}
}

export function subtraction() {
    return {type: SUBTRACTION}
}

Design sketch

In this way we combine Redux and react, but we may find it troublesome because we need to pass store and action creation functions to sub-components. When we have more actions and more sub-components, we need to pass store and a large number of action creation functions layer by layer many times. This can be very cumbersome, so we can use the react-redux library to help us implement this cumbersome process.

III. Use of react-redux

1.Provider

Reaction-redux provides us with a Provider component, which we can write on the outer layer, so that all components wrapped by Provider can get state through props, no matter how deep each component is hidden.
The Provider component accepts only one attribute, which is store.

So our index.js code becomes the following:

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import {reducer} from './index.redux'
import App from './App'

//Create a store
const store = createStore(reducer);

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.querySelector('#root'));

2.connect

Of course, only the Provider component is not enough. We also need connect to help us get state and action. Yes, connect is to help us get state and action.

Then the problem arises. Our component does not need all the States and actions in the project, but only a part of them. So connect will accept two parameters. The first parameter can help us filter the States and the second parameter can help us filter the actions.
We can write these two parameters in the form of functions.
Parametric 1,

function mapStateToProps(state) {
    return {
        money: state.money
    }
}

Parametric 2,

function actionCreators() {
    return {
        subtraction,
        add
    }
}

We can see that both functions return an object. The first function returns the state we need, and the second function returns the action creation function we need.

So the app.js code becomes like this:

import React from 'react'
import {connect} from 'react-redux'
import {add, subtraction} from './index.redux'

class App extends React.Component {
    render() {
        //Because of connect ion, state and action are already available from attributes.
        const {money, add, subtraction} = this.props;

        return <div>
            <h1>I have{money}element</h1>

            {/*We don't need dispatch at this time.*/}
            <button onClick={add}>
                //Pick up a dollar
            </button>

            <button onClick={subtraction}>
                //One dollar was lost.
            </button>
        </div>
    }
}

//The parameters required for connect ion
//The function returns the state we need, we need money, and we take money out of the state.
//If we need a house, add a house: state.
function mapStateToProps(state) {
    return {
        money: state.money
    }
}

//The second parameter required by connect
//Return the action creation function we need
function actionCreators() {
    return {
        subtraction,
        add
    }
}

//Both of the above functions return objects

//Pass state and action creation functions as attributes to components through connect ion
export default App = connect(mapStateToProps, actionCreators())(App);

It would be better if we were familiar with the grammar of the es6 decorator, which would make our code more elegant.
app.js

import React from 'react'
import {connect} from 'react-redux'
import {add, subtraction} from './index.redux'

@connect(
    state => ({money: state.money}),
    {
        subtraction,
        add
    })
export default class App extends React.Component {
    render() {
        //Because of connect ion, state and action are already available from attributes.
        const {money, add, subtraction} = this.props;

        return <div>
            <h1>I have{money}element</h1>

            {/*We don't need dispatch at this time.*/}
            <button onClick={add}>
                //Pick up a dollar
            </button>

            <button onClick={subtraction}>
                //One dollar was lost.
            </button>
        </div>
    }
}

Looking back at the first pictures here, you can see exactly how redux works.

Topics: Javascript React Attribute Vue