Preface 🤔

  • ReactIt’s a one-way data flow. Data flows throughpropsFrom parent node to child node. If some of the top levelpropsChanged,ReactAll child nodes are re-rendered. Note ⚠ ️ :propsIs read-only (that is, cannot be usedthis.propsDirectly modifyingprops), which is used to pass data and configuration throughout the component tree.
  • Each component has its ownstate.statepropsThe difference betweenstateExists only within components. Note ⚠️ : can only be called from the current componentthis.setStateMethods to modifystateValue (Cannot be modified directlythis.state).
  • As you can see, there are two ways to update a child component. One is to change the child component’s ownstateValue, and the update child received from the parentthis.propsValue to be updated.
  • inReactMost of the time during project development, we need to have components share some data. In general, we can pass data between components (throughpropsHowever, when the data needs to be passed between non-parent-child components, it becomes cumbersome to operate and makes the code less readable, so we need to use itstate(Status) management tool.
  • Common status management tools areredux.mobx. Due to thereduxProvides an entire architecture for state management, with clear constraints, suitable for use in massively multiplayer applications. This article introduces how to useReactUsed in the projectreduxPerform status management.

Get down to business 🥰

  • This section mainly introducesreduxreact-routerBasic knowledge 📖 and related configurations 👩🏾💻.

redux

The basic concept

  • reduxThis mode applies to scenarios with multiple interactions and multiple data sources. From a component perspective, we can consider using it in a project if our application has the following scenariosredux:
    • 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
  • When our application conforms to the scenarios mentioned above, if not usedreduxOr other state management tools, if state reading and writing are not handled according to certain rules, the readability of project code will be greatly reduced, which is not conducive to the improvement of team development efficiency.

  • As the picture above shows,reduxBy putting all thestateCentralize to the top of the component, providing flexibility to allstateDistribute to all components as needed.
  • reduxThe three principles of:
    • Application-widestateAll stored in one treeobject tree, andobject treeExists only in onestoreThis does not mean usereduxI need to put all of thestateenduresreduxComponents can still maintain themselvesstate).
    • stateIt’s read-only.stateChanges to the view (view). Out of reach of usersstate, can only touch the view, the only changestateIs triggered in the viewaction.actionIs a generic object that describes events that have occurred.
    • usereducersTo perform thestateThe update.reducersPhi is a pure function that acceptsactionAnd the currentstateAs an argument, a new one is returned by calculationstateTo update the view.

  • As the picture above shows,reduxThe workflow of the
    • First, the user passes through the viewstore.dispatchMethods aaction.
    • And then,storeAutomatically callreducersAnd pass in two arguments: currentstateAnd receivedaction.reducersWill return a newstate
    • Finally, whenstoreListen to thestateThe listener function is called to trigger the view’s re-rendering.
  • ⚡⚡️⚡ port:

API

store
  • storeThat’s where the data is stored, and there can only be one in the entire applicationstore.
  • reduxprovidecreateStoreThis function is used to create astoreTo store the entire applicationstate:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);
Copy the code
  • As you can see,createStoreacceptreducer, the initialstate(Optional) and enhancer as arguments, return a new onestoreObject.
state
  • storeObject contains all data. If you want the data at a certain point in time, you have to be rightstoreGenerate a snapshot. The collection of data at this point in time is calledstate.
  • If you want to get the current momentstate, can be accessed throughstore.getState()Methods to get:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);

const state = store.getState();
Copy the code
action
  • stateChange, which causes the view to change. However, the user cannot reach itstate, can only touch the view. So,stateThe change must be initiated by the view.
  • actionThat’s notifications from the view, notificationsstoreAt this timestateThat should change.
  • actionIs an object. One of thetypeAttributes are required to representactionThe name of the. Other attributes can be set freely, the community has a specification to refer to:
const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'// Optional attribute};Copy the code
  • The code above defines a name calledADD_TODOaction, the data information it carries isLearn Redux.
Action Creator
  • viewThere will be as many messages as there are to sendactionIf they were written by hand, it would be very troublesome.
  • You can define a function to generateactionThis function is calledAction Creator, as in the following codeaddTodoFunction:
const ADD_TODO = 'add TODO;

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

const action = addTodo('Learn Redux');
Copy the code
  • redux-actionsIs a practical library that lets you writereduxState management becomes easy. The library providescreateActionThe action creator () method is used to create an action creator:
import { createAction } from "redux-actions"

export const INCREMENT = 'INCREMENT'
export const increment = createAction(INCREMENT)
Copy the code
  • The code above defines an actionINCREMENTAnd then throughcreateActionCreated a correspondingAction Creator:
    • callincrement()Is returned{ type: 'INCREMENT' }
    • callincrement(10)return{ type: 'INCREMENT', payload: 10 }
store.dispatch()
  • store.dispatch()It’s view emittedactionThe only method that accepts aactionObject as argument:
import { createStore } from 'redux';
const store = createStore(reducer, [preloadedState], enhancer);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
Copy the code
  • In combination withAction Creator, this code can be rewritten as follows:
import { createStore } from 'redux';
import { createAction } from "redux-actions"
const store = createStore(reducer, [preloadedState], enhancer);

const ADD_TODO = 'ADD_TODO';
const add_todo = createAction('ADD_TODO'); // Create Action Creator store.dispatch(add_todo('Learn Redux'));
Copy the code
reducer
  • storereceivedactionLater, a new one must be givenstate, so that the view is updated.stateThe calculation (update) process is passedreducerThe implementation.
  • reducerPhi is a function that acceptsactionAnd the currentstateAs an argument, return a new onestate:
const reducer = function (state, action) {
  // ...
  return new_state;
};
Copy the code
  • In order to implement the callstore.dispatchMethod is executed automaticallyreducerFunction needed before creatingstoreWhen will bereducerThe incomingcreateStoreMethods:
import { createStore } from 'redux';
const reducer = function (state, action) {
  // ...
  return new_state;
};
const store = createStore(reducer);
Copy the code
  • In the code above,createStoreMethod acceptsreducerAs an argument, a new one is generatedstore. Later whenever the view is usedstore.dispatchSent to thestoreA newactionIs automatically calledreducerFunction to get the updatedstate.
  • redux-actionsprovideshandleActionsMethod is used to process more than oneaction:
// handleActions(reducerMap, defaultState) import {handleActions} from'redux-actions';
const initialState = { 
  counter: 0 
};

const reducer = handleActions(
  {
    INCREMENT: (state, action) => ({
      counter: state.counter + action.payload
    }),
    DECREMENT: (state, action) => ({
      counter: state.counter - action.payload
    })
  },
  initialState,
);
Copy the code

Split and merge reducer

  • As mentioned earlier, in areactYou can only have one applicationstoreUsed to store applicationsstate. Component callsactionFunction to pass data toreducer.reducerUpdate the corresponding according to the datastate.
  • For large applications, yesstateMust be so large that it causesreducerThe complexity of the

Break up

  • At this time, you can consider thereducerBreak it up into separate functions and make each function responsible for its own managementstatePart of.

merge

  • reduxprovidescombineReducersAuxiliary functions can be independently dispersedreducerMerged into one finalreducerFunction, and then in the creationstoreascreateStoreThe parameter is passed in.
  • We can put all the pieces according to the business needsreducerPut it in different directories, then introduce it in one file, and finally merge itreducerExport:
// src/model/reducers.ts
import { combineReducers } from 'redux';
import UI from './UI/reducers';
import user from './user/reducers';
import content from './content/reducers';

const rootReducer = combineReducers({
  UI,
  user,
  content,
});

export default rootReducer;
Copy the code

Middleware and asynchronous operations

  • rightreduxIn terms of synchronization, it means when the view is emittedactionLater,reducerWork out immediatelystate(the originalreduxWorkflow), while asynchrony refers to inactionOnce issued, it is executed after a certain period of timereducer.
  • Synchronization usually occurs nativereduxIn most real world scenarios, asynchronous operations are needed more often:actionAfter issuing, before enteringreducerAn asynchronous task, such as sending, needs to be completed firstajaxRequest and get the data before you enterreducerPerforms calculations and pairsstateUpdate.
  • Apparently nativereduxAsynchronous operations are not supported, and a new tool, middleware, is needed to handle this business scenario. Essentially, middleware is rightstore.dispatchThe method is extended.
  • Middleware provides a locationactionAfter launch, arrivereducerPrevious extension point: namely passstore.dispatchMethod issuedactionIt passes through the middleware and finally arrivesreducer.
  • We can use middleware for logging (redux-logger), create crash reports (write your owncrashReporter), call the asynchronous interface (redux-saga) or routing (connected-react-router) and other operations.
  • reduxProvides a nativeapplyMiddlewareMethod, which assembles all the middleware into an array and executes it in turn. If you want to useredux-loggerTo implement the logging function, the usage is as follows:
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);
Copy the code
  • If there are more than one middleware, the middleware is passed in as parameters in turnapplyMiddlewareMethod:
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
import createSagaMiddleware from 'redux-saga'; const logger = createLogger(); Const sagaMiddleware = createSagaMiddleware(); // Call the asynchronous interfaceletmiddleware = [sagaMiddleware]; middleware.push(logger); Const store = createStore(reducer, // initial_state applyMiddleware(... middleware) );Copy the code
  • Note that ⚠️ is:
    • createStoreThe method can optionally accept the initial state of the entire application. If the initial state is passed in,applyMiddlewareNeed to be the third parameter.
    • Some middleware have order requirements that should be documented before use (e.gredux-loggerBe sure to put it last, otherwise the output will be incorrect.

react-redux

The concept is introduced

  • Described in the previous sectionreduxItself is one that can be combinedreact.vue.angularEven nativejavaScriptThe state library used by the application.
  • In order to makereduxHelp us managereactThe state of the application needs to be changedreduxreactThe connection, officially provided react-reduxLibrary (This library can be selected or used onlyredux).
  • react-reduxDivide all components into UI components and container components:
    • The UI component is only responsible for rendering the UI, and does not contain state (this.state), all data are provided bythis.propsProvide and do not use anyreduxThe API.
    • Container components manage data and business logic and contain state (this.state), availablereduxThe API.
  • In short, the container component, as the parent of the UI component, is responsible for communicating with the outside world and passing data throughpropsRender the view to the UI component.
  • react-reduxSpecifies that all UI components are provided by the user, while container components are provided byreact-reduxAutomatic generation. In other words, the user is responsible for the visual layer, and the state management is all overreact-redux.

API

The connect method
  • react-reduxprovidesconnectMethod to generate UI components into container components:
import { connect } from 'react-redux'class Dashboard extends React.Component { ... } const mapStateToProps = (state) => {returnLoading: state.loading,}} // Export container components automatically generated by the connect methodexportDefault connect(mapStateToProps, // Optional // mapDispatchToProps, // optional)(Dashboard)Copy the code
  • As you can see from the code above,connectMethod takes two optional parameters to define the business logic of the container component:
    • mapStateToPropsResponsible for input logic, soonstateMap to parameters passed into the UI component (props)
    • mapDispatchToPropsResponsible for output logic that maps user actions to UI componentsaction
  • Note ⚠️ : whenconnectWhen the method does not pass in any arguments, the generated container component can be thought of as a wrapper around the UI component without any business logic:
    • omitmapStateToPropsParameter, the UI component will not subscribestore, i.e.,storeUpdates to the UI component do not cause updates to the UI component.
    • omitmapDispatchToPropsParameter, the UI component does not treat the user’s actions asactionSend data tostore, needs to be called manually in the componentstore.dispatchMethods.

mapStateToProps

  • mapStateToPropsIs a function that sets up a delta fromstateObject (external) to the UI componentpropsObject mapping. This function subscribes to the entire applicationstoreAnd every timestateWhen updates are made, the UI component’s parameters are recalculated automatically, triggering a re-rendering of the UI component.
  • mapStateToPropsThe first argument to is alwaysstateObject, and optionally a second argument, representing the container componentpropsObject:
// The container component code // <Dashboard showType="SHOW_ALL">
//      All
//    </Dashboard>
const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.showType === "SHOW_ALL",
    loading: state.loading,
  }
}
Copy the code
  • useownPropsAs a parameter, if the parameters of the container component change, it also causes the UI component to re-render.

mapDispatchToProps

  • mapDispatchToPropsconnectThe second argument to the function, which creates the UI component’s argument tostore.dispatchMethod mapping.
  • Because it is mostly used in projectsmapDispatchToPropsI won’t go into detail here. aboutmapStateToProps,mapDispatchToPropsconnectA more detailed usage description of theThe document.
The Provider component
  • useconnectAfter the method generates the container component, it needs to be picked up by the container componentstateObject to generate parameters for the UI component.
  • react-reduxprovidesProviderComponent, which is available to the container componentstate, the specific usage is neededProviderComponents wrap the root component of a project (such as App) so that all of the root’s children are available by defaultstateObject:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store/configureStore';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'));Copy the code

react-router

  • react-routerIs a fullreactThe routing solution that it holdsUIURLSynchronization. In the project we are using the latestv4Version.
  • Note that ⚠️ should not be installed directly during developmentreact-router, because 👉 : inv4Version of thereact-routerDivided into three packages:react-routerreact-router-domreact-router-native, their differences are as follows:
    • react-router: Provides core routing components and functions.
    • react-router-dom: Provides routing components and functions used by the browser.
    • react-router-native: providereact-nativeRouting components and functions used by the platform.
  • When ourreactApplications are used at the same timereact-routerredux, the two can be further integrated to achieve:
    • willrouterThe data andstoreAnd can be synchronized fromstoreaccessrouterData, availablethis.props.dispatchMethods to sendaction.
    • throughdispatch actionsNavigation, personal understanding is availablestore.dispatch(push('routerName'))Switch routes.
    • inredux devtoolsSupports time travel debugging of route changes.
  • To achieve the above goals, you can passconnected-react-routerhistoryTwo libraries are implemented as follows:
    • When creating astoreFiles to add configuration, including creationhistoryObject, usageconnected-react-routerTo provide theconnectRouterMethods andhistoryObject creationroot reducer, the use ofconnected-react-routerTo provide therouterMiddlewareMiddleware andhistoryObject implementationdispatch actionsNavigation.
    import { connectRouter, routerMiddleware } from 'connected-react-router';
    import createHistory from 'history/createBrowserHistory';
    import { createStore, applyMiddleware } from 'redux';
    import { createLogger } from 'redux-logger';
    import createSagaMiddleware from 'redux-saga';
    import reducer from '.. /model/reducers';
    
    export const history= createHistory(); const sagaMiddleware = createSagaMiddleware(); // Call the asynchronous interfacelet middleware = [sagaMiddleware, routerMiddleware(history)]; const logger = createLogger(); // Log middleware. Push (logger); const initialState = {}; const store = createStore( connectRouter(history)(reducer), initialState, applyMiddleware(... middleware) );Copy the code
    • File in the project entryindex.jsAdd configuration to the root component, including usingconnected-react-routerTo provide theConnectedRouterComponent package routing willConnectedRouterComponent asProviderAnd will be in thestoreCreated in thehistoryObject to be introduced aspropsProperties of the incomingConnectedRouterComponents:
    import * as React from 'react';
    import * as ReactDOM from 'react-dom';
    import { Provider } from 'react-redux'
    import { ConnectedRouter } from 'connected-react-router'
    import App from './App'
    import rootSaga from './model/sagas';
    import { store, history } from './store/configureStore';
    
    ReactDOM.render(
      <Provider store={store}>
        <ConnectedRouter history= {history}>
          <App />
        </ConnectedRouter>
      </Provider>,
      document.getElementById('root'));Copy the code
  • And that’s donereact-routerreduxDeep integration ✌️.

Conclusion 👀

  • This article introduces how to useReactUsed in the projectreduxState management, and the relevant basic knowledge of the introduction and display of the complete code.
  • For details, see 👉 : React + typescript project customization process.

If there are any omissions in the above content, please leave a message ✍️ to point out, and progress together 💪💪💪

If you find this article helpful, 🏀🏀 leave your precious 👍

Resources 📖

  1. Redux or Mobx, let me solve your confusion!
  2. Build React applications from scratch — React Application Architecture
  3. Introduction to Redux — Middleware and Asynchronous Operations
  4. Redux Tutorial — How to use React-redux