By Mika

React + Redux

When you use Redux for state management in React projects, you need to know the structure and timing of reder, action, AND API files. This chapter assumes that readers have already made a preliminary use of “redux.”

The general project

By general project, the blogger refers to a project that only needs a “state” warehouse for state management, suitable for general corporate project production, personal learning, etc. Such projects do not require additional combineReducers to integrate your “state” repository, just a single “state” repository for data management.

It is worth noting that a large number of “states” here do not fail to meet the principle of “Redux” single warehouse, but due to the large amount of data involved and the segmentation of requirements and functions, each “state” warehouse needs to be stored in different files, which is convenient to find and modify, and at the same time reduce the volume of a single “state” warehouse. It can effectively reduce space usage and avoid fetching a huge “state” warehouse every time store.getState() is used.

So for the general volume of the “state” warehouse project, how should we better organize our file structure?

When we first need to make a “state” change to the repository, where do we change it? If the reducer needs to be modified, action needs to be modified, and action request type needs to be modified, the reducer can be placed in the Store folder as a whole. In this way, the reducer can be quickly located when the modification is required.

/src
    /api
        server.js
        api.js
    /page
    /store
        actionCreators.js
        actionTypes.js
        index.js
        reducer.js
    index.js  
Copy the code

Above is part of my file structure, let’s take a look at how we design each file structure internally, in this link, I will show this part of the file from one of my previous projects, and make as many detailed comments as possible, I hope you can find something.

OK, let’s start with the basic entry file SRC /index.js.

Index.js (entry file)

// The necessary React import
import React from 'react';
import ReactDOM from 'react-dom';
// The necessary React import
import { createStore } from 'redux'
// Create the store
import store from './store'
// Create a router
import MyRouter from './pages/router'

ReactDOM.render(
    // Bind to the entire project
    <MyRouter store={store} />,
    document.getElementById('root')
);
Copy the code

This is the entry file for the whole project. It is very simple, and when you need to review the project, you can view the “redux” section directly in store/index.js

store/actionTypes.js

// Control the left navigation bar to extend revenue
export const CONTROL_LEFT_PAGE = 'controlLeftPage';
// Control the right subject line to extend revenue
export const CONTROL_RIGHT_PAGE = 'controlRightPage';
Copy the code

Why do we need a JS file like this? Can’t we just use {type: ‘controlRightPage’} in “action” instead of “type”?

“Absolutely!” But there’s always a reason for that, so let’s look at a simple example.

Simple example

One day, I have a new requirement. There is an action that needs to modify the data in state. The default value of “String” in “Reducer” is “type”, which is “setInputVal”. A normal need. Shall we get started? We need to write something like the following structure in our “reducer”.

// This is the reducer
export default (state = defaultState, action) => {
    let newState;
    switch (action.type) {
        case 'setInputVal':
            newState = JSON.parse(JSON.stringify(state));
            newState.leftPageFlag = action.flag;
            return newState;
    }
    // If there is no corresponding action.type, return the unmodified state
    return state;
}
Copy the code

Instead, we would have something like the following structure in our “action”.

// This is an action
export const setInput = (val) = > ({
    type: 'setInputValue',
    val
})
Copy the code

On the face of it, there’s nothing wrong, but what happens when you type your “type” by mistake, and I’ve simulated an error in our “coding”? Yes, there will be no error, your program will execute normally, but you will find that the operation on this function in the project is invalid, and you will look at every line of your logic over and over again, and you will say, oh, I made a mistake here, I will correct it.

Let’s see what happens if we use a custom parameter to save “type”.

// This is a custom type argument
export const SET_INPUT_VAL = 'setInputVal';
Copy the code
// This is the reducer
export default (state = defaultState, action) => {
    let newState;
    switch (action.type) {
        case SET_INPUT_VAL:
            newState = JSON.parse(JSON.stringify(state));
            newState.leftPageFlag = action.flag;
            return newState;
    }
    // If there is no corresponding action.type, return the unmodified state
    return state;
}
Copy the code
// This is an action
export const setInput = (val) = > ({
    type: SET_INPUT_VAL,
    val
})
Copy the code

This is something that you might have noticed, and you’ll find that it’s very difficult for you to get an error because the “type” is created using a constant definition, the entire program only uses setInputVal once, and then you rename it in various ways that have nothing to do with the entire program.

If you write SET_INPUT_VAL instead of SET_INPUT_VALUE, your program will tell you that SET_INPUT_VALUE is not defined. After all, “coding” is always wrong, but when you do have something to point you in the right direction, you feel comfortable and your life has a new direction (bloggers also often change “bugs” for a variable name or “string” to drop the keyboard).

store/reducer.js

// Import the type you created
import { CONTROL_LEFT_PAGE, CONTROL_RIGHT_PAGE } from './actionTypes'
/** * This is a state repository **/
const defaultState = {
    // Left navigation flag
    leftPageFlag: false.// Navigation flag on the right
    rightPageFlag: false};
// This is your reducer. Take the default repository or pass in one, and change it according to action
export default (state = defaultState, action) => {
    let newState;
    switch (action.type) {
        // CONTROL_LEFT_PAGE is a string of type defined by itself
        case CONTROL_LEFT_PAGE:
            newState = JSON.parse(JSON.stringify(state));
            newState.leftPageFlag = action.flag;
            return newState;
        case CONTROL_RIGHT_PAGE:
            newState = JSON.parse(JSON.stringify(state));
            newState.rightPageFlag = action.flag;
            return newState;
    }
    return state;
}
Copy the code

The reason why the “state” repository is put here is that when you modify the “reducer”, there is a reference to the “state”, so you can clearly see which part of the “state” you want to modify. Also, switch — case is very intuitive.

store/actionCreators.js

// Import the type you created
import { CONTROL_LEFT_PAGE, CONTROL_RIGHT_PAGE } from './actionTypes'
// Redux-thunk: redux-thunk: redux-thunk
import { getStarArticlesApi } from '.. /api/api'
// Import your store
import store from '.. /store'

// Factory mode

/** * Control the left navigation bar to extend income **/
export const controlLeftPage = (flag) = > ({
    type: CONTROL_LEFT_PAGE,
    // Add parameters to the reducer and change the state according to this logic
    flag
})

/** * Control the right subject line to extend income **/
export const controlRightPage = (flag) = > ({
    type: CONTROL_RIGHT_PAGE,
    // Add parameters to the reducer and change the state according to this logic
    flag
})

/** * Get star articles **/
// Note that the getStarArticles function is not a real action, but only a function that includes the action after the asynchronous operation.
export const getStarArticles = (req) = > {
    return (dispatch) = > {
        // After getStarArticles is executed, the HTTP request is made
        getStarArticlesApi(req).then(res= > {
            // After the request is completed, transfer the result to the Reducer through the Action
            const action = getStarArticlesBack(res);
            dispatch(action);
        }).catch(err= > {
            / / an error
            console.log(err); }}}) /** * Get callback to star article **/ export const getStarArticlesBack = (res) = > ({ type: GET_STAR_ARTICLES, // Add parameters to the reducer and change the state according to this logic res })  Copy the code

“ActionCreators” is the place to create your “action”, and whenever you need to add an “action” you can define your “action” in the actiontypes.js file after defining the “type”.

If you need to perform an “HTTP” request within an action, “Redux” itself can’t do that, so we introduced the “NPM” library redux-Thunk, which allows us to do some processing on the “action” before “dispatch” it. For example, some asynchronous operations (” HTTP “requests), so I’ve left the” action “getStarArticles for this part to give you an example.

store/index.js

// The necessary redux method
import { createStore, applyMiddleware, compose } from 'redux';
// One of my reducer
import reducer from './reducer'
// Redux-Thunk is the middleware of the request, which will be mentioned in the action section
import thunk from 'redux-thunk' 

// Enhance function one step method, execute two functions
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;

/ / middleware
const enhancer = composeEnhancers(applyMiddleware(thunk));

/ / integrated store
const store = createStore(
    reducer, /* preloadedState, */
    enhancer
);

/ / export
export default store;
 Copy the code

This file combines “reducer” (and perhaps middleware “thunk”) and output the reducer as a store object. If you don’t know more about the store, you’ll see the following: Redux’s createStore implementation [1].

api/server.js

import axios from 'axios'
import qs from 'qs'

let http = {
    post: ' '.get: ' '
}

http.post = function (api, data) {
    let params = qs.stringify(data);
    return new Promise((resolve, reject) = > {
        axios.post(api, params).then(res= > {
            resolve(res.data)
        }).catch(err= > {
            reject(err.data)
        })
    })
}

http.get = function (api, data) {
    let params = qs.stringify(data);
    return new Promise((resolve, reject) = > {
        axios.get(api, params).then(res= > {
            resolve(res.data)
        }).catch(err= > {
            reject(err.data)
        })
    })
}
export default http

Copy the code

I don’t want to comment too much on this, because this is actually an “AXIos” wrapper that I am familiar with, so you just need to know that the exported HTTP is an object with two object methods, namely GET and POST, returning a Promise object.

api/api.js

// HTTP object with two object methods: get and POST
import http from './server'
// Get star articles, not details, with length
export const getStarArticlesApi = p= > http.post('/getStarArticles', p);
// The number of groups and the name of the group are obtained
export const getAllGroupLengthApi = p= > http.post('/getAllGroupLength', p);
Copy the code

This “API” method has two advantages, the first and the definition of “type” is actually a reason, can avoid the “API” write wrong, second, when you define can there is no need to make sure that you need to the backend is a what kind of data type, direct use of p can directly “instead of all the values you want to pass”, For your initial definition.

This is the opposite of “TypeScript” thinking, because TS expects you to explicitly define the detailed type of this location, and if you’re using “JS”, you don’t need to restrict it. So here’s a different way to write it in the “TS+ React + Redux” project, and if you’re interested you can comment on it @me. If you are not clear about what I have said, you can also leave your email address. I will send you the code of this chapter for your experiment and test.

I’m mika

Reference

[1]

Redux createStore implementation: https://juejin.im/post/6844904147100123144

This article was typeset using MDNICE