This article covers the Redux architecture, including the introduction and usage of basic concepts, and the Principles of Redux. github.com/reduxjs

You probably don’t need Redux

To be clear, Redux is a useful architecture, but you don’t have to use it. Someone once said this sentence.

  • If you don’t know if you need Redux, you don’t need it.
  • You only need Redux when you have a React problem that really can’t be solved.

Simply put, if your UI layer is very simple and doesn’t have a lot of interaction, Redux is unnecessary and adds complexity.

  • The way users use it is very simple
  • There is no collaboration between users
  • There is no heavy interaction with the server and no WebSocket
  • The View layer only gets data from a single source

If:

  • The way users use it is complex
  • Users of different identities have different usage modes (for example, ordinary users and administrators)
  • Multiple users can collaborate
  • A lot of interaction with the server, or using websockets
  • The View gets data from multiple sources

These are the scenarios for Redux: multiple interactions, multiple data sources.

From a component perspective, consider using Redux if your application has the following scenarios.

  • The state of a component that needs to be shared
  • A state needs to be available anywhere
  • A component needs to change global state
  • One component needs to change the state of another component

Redux, on the other hand, is just one solution to the Web architecture, and other alternatives are possible. For example, if it’s React, you can also use Recoil.

Design idea

(1) Web application is a state machine, and view and state are one-to-one corresponding. (2) All states are stored in one object.

Basic concepts and apis

store

A Store is a place where data is stored, you can think of it as a container. There can only be one Store for the entire app. Redux provides the createStore function to generate a Store.

state

The Store object contains all the data. If you want data at a point in time, take a snapshot of the Store. This collection of data at this point in time is called State. The current State can be obtained from store.getState().

Redux states that one State corresponds to one View. As long as the State is the same, the View is the same. You know what State is, you know what View is, and vice versa.

action

If the State changes, the View changes. However, the user does not touch State, only View. Therefore, the change in State must be caused by the View. An Action is a notification from the View that the State should change. Action is an object. The type attribute is required and represents the name of the Action. Other attributes can be set freely, and the community has a specification to refer to.

const action = {   
  type: 'ADD_TODO'.payload: 'Learn Redux' 
}; 
Copy the code

Actions describe what is currently happening. The only way to change State is to use Action. It will ship data to the Store.

Action Creator

There are as many actions as there are messages that the View wants to send. If they were all handwritten, it would be troublesome. You can define a function to generate Action, called Action Creator.

const ADD_TODO = 'add TODO; 

function addTodo(text) {   
  return {     
    type: ADD_TODO, text}};const action = addTodo('Learn Redux'); 
Copy the code

In the code above, the addTodo function is an Action Creator.

Think about it: the above code doesn’t subtract much code from the writing, and seems useless. So what’s the benefit of this? Using the above method, we write the action every time when we don’t need dispatch, which has the effect of reuse and reduces errors.

store.dispatch

Store.dispatch () is the only way a View can issue an Action.

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

store.dispatch({
  type: 'ADD_TODO'.payload: 'Learn Redux'
});
Copy the code

Reducer

When the Store receives the Action, it must give a new State for the View to change. This State calculation process is called Reducer. Reducer is a function that takes Action and the current State as parameters and returns a new State.

const reducer = function (state, action) {
  // ...
  return new_state;
};
Copy the code

The initial State of the entire application can be used as the default value for State. Here is a practical example.

const defaultState = 0;
const reducer = (state = defaultState, action) = > {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      returnstate; }};const state = reducer(1, {
  type: 'ADD'.payload: 2
});
Copy the code

In the code above, the createStore takes the Reducer as an argument and generates a new Store. In the future, when a new Action is sent by Store. dispatch, Reducer will be automatically called to obtain the new State.

Why is this function called Reducer? Because it can be used as a parameter to the array’s Reduce method. Consider the following example, where a sequence of Action objects is an array.

const actions = [
  { type: 'ADD'.payload: 0 },
  { type: 'ADD'.payload: 1 },
  { type: 'ADD'.payload: 2}];const total = actions.reduce(reducer, 0); / / 3
Copy the code

If it helps to follow the above explanation, it can be followed. What if someone asks you what reducer is? How to accurately describe the reducer role in one sentence?

Reducer defines data update rules.

So what are combineReducers? If the reducer in the project is very large and it is very inconvenient to maintain, the reducer can be divided into sub-reducer, combineReducers is to synthesize a total reducer from the sub-reducer.

The principle of

CreateStore and combineReducers

function createStore(reducer, initialState) {
    let currentState = initialState;

    const getState = () = > currentState;
    let listeners = [];

    const subscribe = (sub) = > {
        listeners.push(sub);

        // Unsubscribe
        return () = > {
            let index = listeners.findIndex((s) = > s === sub);
            if(index ! = = -1) {
                listeners.splice(index, 1); }}}const dispatch = (action) = > {
        const newState = reducer(currentState, action);

        if(newState ! == currentState) { currentState = newState; listeners.forEach((sub) = >{ sub && sub(); }); }}return {
        getState,
        dispatch,
        subscribe
    };
}

function combineReducers(reducers){
    const reducerKeys = Object.keys(reducers);
    const finalReducers = {};

    // Remove attributes in reducers that are not functions
    reducerKeys.forEach((key) = > {
        if(typeof reducers[key] === 'function') { finalReducers[key] = reducers[key]; }});return function combination(state, action) {
        let hasChanged = false;
        const nextState = {};

        Object.keys(finalReducers).forEach((key) = > {
            const reducer = finalReducers[key];

            const previousStateForKey = state[key];
            const nextStateForKey = reducer(previousStateForKey, action);

            if(nextStateForKey == null) {
                throw new Error('When called with an action, the reducer returned undefined'); } nextState[key] = nextStateForKey; hasChanged = hasChanged || nextStateForKey ! == previousStateForKey; }); hasChanged = hasChanged ||Object.keys(state).length ! = =Object.keys(nextState).length;

        return hasChanged ? nextState : state;
    };;
}

export {
    createStore,
    combineReducers
};
Copy the code

Simple implementation of combineReducer

const combineReducers = reducers= > {
  return (state = {}, action) = > {
    return Object.keys(reducers).reduce(
      (nextState, key) = > {
        nextState[key] = reducers[key](state[key], action);
        returnnextState; }, {}); }; }Copy the code

We have implemented our own redux, including createStore and combineReducers. Can we use it? Verify that some of our implementations are the same as real Redux?

Use in HTML

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
    <style>
        .con{
            border: 1px solid green; 
            margin: 20px;
            min-height: 50px;
        }
    </style>
</head>
<body>
    <div>
        <div class="con">
            <button id="btnAdd">increase</button>
            <button id="btnMinus">To reduce</button>
            <button id="btnCancel">cancel</button>
            <div>
                <span id="span1">
                </span>
            </div>
        </div>
        <div class="con">
            <input id="msgInput" type="text"/>
            <button id="btnMsg">change</button>
            <div>
                <span id="msg2">
                </span>
            </div>
        </div>
    </div>
    <script type="module">
        import { createStore, combineReducers } from './redux.js';

        function counterReducer(state, action){
            switch(action.type) {
                case 'ADD':
                    return {
                        counter: state.counter + Number(action.payload)
                    };
                case 'MINUS':
                    return {
                        counter: state.counter - Number(action.payload)
                    };
                default:}return state;
        }

        function messageReducer(state, action){
            switch(action.type){
                case 'CHANGEMSG':
                    return {
                        msg: action.payload
                    };
                default:}return state;
        }

        const reducer = combineReducers({
            count: counterReducer,
            message: messageReducer
        });
        const store = createStore(reducer, {
            count: {
                counter: 0
            },
            message: {
                msg: ' '}});let btnAdd = document.querySelector('#btnAdd');
        let btnMinus = document.querySelector('#btnMinus');
        let btnCancel = document.querySelector('#btnCancel');
        let btnMsg = document.querySelector('#btnMsg');

        btnAdd.onclick = function(){
            store.dispatch({
                type: 'ADD'.payload: 1
            });
        }
        btnMinus.onclick = function(){
            store.dispatch({
                type: 'MINUS'.payload: 1
            });
        }
        btnCancel.onclick = function(){
            unsubscribe();
        }

        btnMsg.onclick = function(){
            let input = document.querySelector('#msgInput');

            if(input.value) {
                store.dispatch({
                    type: 'CHANGEMSG'.payload: input.value }); }}const unsubscribe = store.subscribe(() = > {
            renderDOM();
        });

        function renderDOM(){
            let state = store.getState();
            console.log(state);

            let span1 = document.querySelector('#span1');
            span1.innerHTML = state.count.counter;

            let msg = document.querySelector('#msg2');
            msg.innerHTML = state.message.msg;
        }
        renderDOM();
    </script>
</body>
</html>
Copy the code

Used in React

We used React as an example to use our own Redux. In fact, we can use our own Redux in any javascript framework.

redux/index.js

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

export const countReducer = (state, action) = > {
    switch (action.type) {
        case 'ADD':
            return {
                count: state.count + action.payload
            };
        case 'MINUS':
            return {
                count: state.count - action.payload
            };
        case 'RESET':
            return {
                count: 0
            };
        default:
            return state
    }
}

export const msgReducer = (state, action) = > {
    switch (action.type) {
        case 'CHANGE':
            return {
                msg: action.payload
            };
        default:
            return state
    }
}

const reducer = combineReducers({
    counter: countReducer,
    message: msgReducer
});

export default createStore(reducer, {
    counter: {
        count: 0
    },
    message: {
        msg: ' '}});Copy the code

components/ReduxPage.js

import React, { Component } from 'react';
import store from '.. /redux';

export default class ReduxPage extends Component {
    constructor(props){
        super(props);
    }

    addBtnClick = () = >{
        store.dispatch({
            type: 'ADD'.payload: 1
        });
    }

    componentDidMount(){
        store.subscribe(() = > {
            this.forceUpdate();
        });
    }

    render() {
        const { count } = store.getState().counter;
        
        return (
            <div>
                <div>
                    <span>{count}</span>
                    <button onClick={ this.addBtnClick} >increase</button>
                </div>
            </div>)}}Copy the code

components/ReduxPage2.js

import React, { Component } from 'react';
import store from '.. /redux';

export default class ReduxPage2 extends Component {
    constructor(props){
        super(props);

        this.state = {
            value: ' '
        };
    }

    msgChange = () = >{
        if (this.state.value) {
            store.dispatch({
                type: 'CHANGE'.payload: this.state.value
            });
        }
    }

    input = (e) = > {
        if(e.target.value) {
            this.setState({
                value: e.target.value }); }}componentDidMount(){
        store.subscribe(() = > {
            this.forceUpdate();
        });
    }

    render() {
        const { value } = this.state;
        const { msg } = store.getState().message;

        return (
            <div>
                <div>
                    <input type="text" value={value} onInput={ this.input} / >
                    <button onClick={ this.msgChange} >change</button>
                </div>
                <span>{ msg }</span>
            </div>)}}Copy the code

app.js

import logo from './logo.svg';
import './App.css';
import ReduxPage from './components/ReduxPage';
import ReduxPage2 from './components/ReduxPage2';

function App() {
  return (
    <div className="App">
      <ReduxPage/>
      <ReduxPage2/>
    </div>
  );
}

export default App;
Copy the code

The bindActionCreators implementation is simple and can be found at github.com/reduxjs/red…

The applyMiddleware and Compose implementations are a little more complex, and you can refer to them. I’ll explain its implementation in a future article. Github.com/reduxjs/red…