Redux learning notes – Vol.2 – Basics

Time:2022-1-13

Prev: Redux learning notes – Vol.1 – Introduction

Action

Action is the payload that transfers data from the application to the store.It is the only source of store data, generally throughstore.dispatch()Pass the action to the store.
For example, chestnuts:

const ADD_TODO = 'ADD_TODO';

//An action can be expressed as:
{
    type: ADD_TODO,
    text: 'Build my first Redux app'
}

To put it bluntly, action is an ordinary JavaScript object, but one thing to note:It is agreed that the object representing action must have a type field to represent the action to be executed.
Minimize data passing in actions

Action Creator

Action creator is the method to generate action.

function addTodo(text){
    return {
        type: 'ADD_TODO',
        text
    }
}

In Redux, you only need to return the result of action creator todispatch()You can initiate a dispatch process.

dispatch(addTodo(text));

Alternatively, create a bound action creator to automatically dispatch:

const boundAddTodo = (text) => dispatch(addTodo(text));
boundAddTodo();

So far, we have written an action js

//action type
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';


//Other constants
export const VisibilityFilters = {
    SHOW_ALL: 'SHOW_ALL',
    SHOW_COMPLETED: 'SHOW_COMPLETED',
    SHOW_ACTIVE: 'SHOW_ACTIVE'
};


//action creator
export function addTodo(text){
    return {
        type: ADD_TODO,
        text
    }
}

export function toggleTodo(index){
    return {
        type: TOGGLE_TODO,
        index
    }
}

export function setVisibilityFilter(filter){
    return {
        type: SET_VISIBILITY_FILTER,
        filter
    }
}

Reducer

With the action, you now need a reducer to indicate how to update the state.

State structure

One thing to be clear is that in the Redux application,All States are stored in a single object.
For example, chestnut, a todo application, needs to save two different kinds of data

  • Currently selected task filter criteria

  • Complete task list

{
    visiibilityFilter: 'SHOW_ALL',
    todos: [
        {
            text: 'Consider using Redux',
            complete: true
        },
        {
            text: 'Keep all state in a single tree',
            complete: false
        }
    ]
}

Process action

Reducer is aPure function, accept the old state and action, and return the new state, such as:

    (previousState, action) => newState

It is very important to keep reducer pure,NeverDo these operations in reducer:

  • Modify the passed in parameters

  • Perform operations with side effects, such as API requests and route jumps

  • Call impure functions, such asDate.now()orMath.random()

What is a pure reducer like?
As long as the parameters passed in are the same, the next state returned by calculation must be the same.
OK, start writing reducer.

import { VisibilityFilters } from './actions';

const initialState = {
    visibilityFilter: VisibilityFilter.SHOW_ALL,
    todo: []
};

function todoApp(state = initialState, action){
    switch (action.type){
        case SET_VISIBILITY_FILTER:
            return  Object.assign({}, state, {
                visibilityFilter: action.filter
            });
        default:
            return state;
    }
}

be careful:

  1. Do not modify state.In the above code, onlyObject.assign()Created a copy.

  2. staydefaultIn case of, return to the oldstateIn case of unknown, be sure to return the old state!

Process multiple actions

Add two firstADD_TODOandTOGGLE_TODO

case ADD_TODO:
    return Object.assign({}, state, {
        todos: [
            ... state. Todos, // ES6 great method
            {
                text: action.text,
                complete: false
            }
        ]
    });
    
case TOGGLE_TODO:
    return Object.assign({}, state, {
        todos: state.todos.map(function(todo, index){
            if (index === action.index){
                return Object.assign({}, todo, {
                    completed: !todo.completed;
                });
            }
            return todo;
        });
    });

Split reducer

function todos(state = [], action){
    switch(action.type){
        case ADD_TODO:
            return [
                ...state,
                {
                    text: action.text,
                    completed: false
                }
            ];
        case TOGGLE_TODO:
            return state.map(function(todo, index){
                if (index === action.index){
                    return Object.assign({}, todo, {
                        completed: !todo.completed
                    });
                }
                return todo;
            });
    }
}

function todoApp(state = initialState, action){
    switch(action.type){
        case SET_VISIBILITY_FILTER:
            return Object.assign({}, state, {
                visibilityFilter: action.filter
            });
        case ADD_TODO:
        case TOGGLE_TODO:
            return Object.assign({}, state, {
                todos: todos(state.todos, action)
            });
        default:
            return state;
    }
}

Todos still accepts state, but the state here becomes an array. Todoapp only passes the part of state that needs to be updated to todos. This is it.Reducer synthesis, is the most basic mode for developing Redux applications.
Use the same method to split the visibilityfilter:

function visibilityFilter(state = SHOW_ALL, action){
    switch(action.type){
        case SET_VISIBILITY_FILTER:
            return action.filter;
        default:
            return state;
    }
}

And modify the total reducer

function todoApp(state = {}, action){
    return {
        visibilityFilter: visibilityFilter(state.visibilityFilter, action),
        todos: todos(state.todos, action)
    };
}

Redux will help you with the merger:

import { combineReducers } from 'redux';

const todoApp = combineReducers({
    visibilityFilter,
    todos
});

export default todoApp;

ES6 great method
combineReducersAn object is accepted and a function is returned. Thus, we can put all top-level reducers into a separate file through > >exportExpose each reducer function, and then useimport * as reducerIntroduce an object with their name as the key:

import { combineReducers } from 'redux';
import * as reducer from './reducers';

const todoApp = combineReducers(reducer);
//ES6 great method!

So far, we have got a completereducers.js

import { combineReducers } from 'redux';
import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions';

function visibilityFilters(state = SHOW_ALL, action){
    switch(action.type){
        case SET_VISIBILITY_FILTER:
            return action.filter;
        default:
            return state;
    }
}

function todos(state = [], action){
    switch(action.type){
        case ADD_TODO:
            return [
                ...state,
                {
                    text: action.text,
                    completed: false
                }
            ];
        case TOGGLE_TODO:
            return state.map(function(todo, index){
                if (index === action.index){
                    return Object.assign({}, todo, {
                        completed: !todo.completed
                    });
                }
                return todo;
            });
        default:
            return state;
    }
}

const todoApp = combineReducer({
    visibilityFilters,
    todos
});

export default todoApp;

Store

Before we learn about store, let’s review itactionandreducer

  • Action: used to indicate “what happened”

  • Reducer: decide how to update based on “what happened”state

What we need to know now is,StoreThe function of is to connect the above two.
Responsibilities of the store:

  • Maintain applicationstate

  • getState()Method to get state

  • dispatch(action)It is used to distribute actions and update state

  • subscribe(listener)Register reducer and return a function that cancels the registration

FYI:Redux has only one single store!When the logic is complex and needs to be split, please start with reducer, Koko.
Now, let’s create a store according to the written reducer, so easy~

import { createStore } from 'redux';
import todoApp from './reducers';

let store = createStore(todoApp);

The second parameter of the createstore () method is optional and is used to set the initial state of state.

let store = createStore(todoApp, initialState);

Initiate actions

Now we have a store. Let’s see how to use it.

import { addTodo, toggleTodo } from './actions';

//Get state
store.getState();

//Register (subscribe), state does not trigger an update, print it
let unsubscribe = store.subscribe(function(){
    console.log(store.getState());
});

//Initiate action
store.dispatch(addTodo('finish your resume.'));
store.dispatch(toggleTodo(0));

//Stop listening for state updates
unsubscribe();

Look what this part has done ↓

//Created a store (there is only one store in the contract!)
import { createStore } from 'redux';
import todoApp from './reducers';

let store = createStore(todoApp);

Strict one-way data flow (Redux core)

  1. launch:store.dispatch(action)

  2. Pass: the store passes the initiated action and current state to the root reducer, which then passes these two parameters to the child reducers;

  3. Update: after the child reducers perform their duties, the root reducers merge their outputs into a single state tree;

  4. Save: store saves the state returned by the reducer, and this time the data flow ends.