Redux

For SPA application front end need to manage the state of the gradually increasing, need to query, update, transfer condition also gradually increased, if let each component are stored state itself, is not influence the operation of application in theory, but in the development and subsequent upgrade maintenance phase, we will spend a lot of energy to query the state change process, In the communication of multiple composite components or the interaction between client and server, we often need to update, maintain and monitor the state of each component.

In this case, wouldn’t it be nice to have a centralized place to manage state? State management is like a centralized general configuration box. When state needs to be updated, we can only input into this configuration box without worrying about how the switch state is distributed internally to each component, so that developers can focus more on the business logic. Today we take a closer look at the redux library to see what it can help us do….

What is a Redux?

“Redux is a pattern and library for managing and updating application state, using events called “actions”. It serves as a centralized store for state that needs to be used across your entire Application, with rules ensuring that the state can only be updated in a predictable fashion.”

The simple meaning is; Redux is a useful architecture for managing and updating application state with operational events. It is used for centralized storage of state across the application, and state updates must be made in a predictable manner.

Why Redux?

“It helps to understand what this “Redux” thing is in the first place. What does It do? What problems does it help me solve? Why would I want to use it?

Redux is a pattern and library for managing and updating application state, using events called “actions”. It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion. “

In fact, state management is not necessary. When your UI layer is simple or there are not many interactions to change state, using state management can increase project complexity. Redux author Daniel Abramov said, “You only need Redux when React doesn’t really solve problems.”

When to use Redux?

1. Multi-interaction and multi-data source scenarios:

  • 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
  • Interact heavily with the server, or useWebSocket
  • The View gets data from multiple sources

2. Component perspective: Redux can be considered in the following scenarios:

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

When this happens, if you don’t use Redux or some other state management tool to handle state reads and writes on a regular basis, your code can quickly become a mess. You need a mechanism to query state, change state, and propagate state changes in one place. In addition, this article focuses more on the data flow at the business model layer, which is the collection of business data, rules, and processes in the domain. It is important to note that state management libraries such as Redux act as an application’s business model layer and are not limited to View layers such as React. If you already understand Redux’s positioning and application scenarios, let’s take a look at how it works.

Design idea

The design idea of Redux is very simple, using teacher Ruan’s two sentences:

  • WebAn application is a state machine, and views and states correspond one to one.
  • All the states are stored in one object.

Redux’s three principles

  • Single data source

    The state of the entire application is stored in an object tree that exists in only one store.

    This makes homogeneous application development very easy. State from the server can be serialized and injected into the client without writing more code. Debugging is also very easy because of the single State Tree. During development, you can save your application’s state locally to speed up development. In addition, features such as undo/redo that were previously difficult to implement become easy thanks to a single State Tree.

  • State is read-only

    The only way to change state is to trigger an action, which is a generic object that describes events that have occurred. This ensures that neither views nor network requests can directly modify state, instead they can only express the intent to change it. Because all changes are centralized and executed in strict sequence, there is no need to worry about race conditions. Actions are just ordinary objects, so they can be logged, serialized, stored, and played back during debugging or testing.

  • Use pure functions to perform modifications

    To describe how an action changes the State Tree, you need to write reducers. Reducer is just pure functions that receive the previous state and action and return the new state. You can start with just a Reducer, but as the application gets bigger, you can break it down into smaller reducers that work independently on different parts of the state tree. Because Reducer is just a function, you can control the order in which they are called, pass in additional data, Even write reusable reducers to handle some common tasks, such as pagers.

The data flow

Strict one-way data flow is at the heart of the Design of the Redux architecture.

This means that all data in the application follows the same lifecycle, which makes the application more predictable and easy to understand. Data formalization is also encouraged to avoid using multiple, independent, duplicate data that cannot be referenced to each other.

Take a look atReactandReduxFlow chart:

Here’s the simplest example:

// Create a basic store
const store =createStore(reducers);
// subscribe() returns a function to unsubscribe the listener
const unsubscribe = store.subscribe(() = >console.log(store.getState()))
// Initiate a series of actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
Copy the code

With these lines of code, we have implemented the overall flow of a data stream callback from dispatch(action)->reducer->subscribe-> View (omit the middleWare part here), without any UI layer in this case, Redux can also complete the data flow independently. Subscribe is the function of subscribing to state changes and updates. View rendering can be registered in the callback function.

Redux implementation

Through state management in the SPA project, we learned what scenarios redux and Redux can solve. Understand what problems Redux solves and how it solves them so you can get a handle on redux’s design. The React as a modular development framework, communication between a large number of components, and communication between components may cross domain multilayer component, or share the same data between multiple components, React with simple components, the father and son of father and son communication can not meet our requirements, so we need to have a space to access and manipulate the public status. Redux provides a solution for managing public state, and we’ll focus on that for the rest of our discussion.

Redux core API implementation

According to the above data flow, Redux is mainly composed of three parts: Store, Reducer and Action. Story is at the heart of the store, it has generated createStore function provided by the story, the function returns three processing functions getState, dispatch, subscribe.

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. Next we write store:

export default function createStore() {
 let state = {} // Public state
 const getState = () = > {} // Stored data, state tree;
 const dispatch = () = > {} // Distribute the action and return an action. This is the only way to change the data in the store;
 const subscribe = () = > {} // Register a listener to be called when store changes.
 return {dispatch,subscribe,getState}
}
Copy the code

GetState () implementationObject 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.

 const getState = () = > {
   return state;
 }
Copy the code

The implementation of dispatch() directly changes state,state.num+’a’. If you change state like this, the result is not what you want. If you can change state at will, it will cause bugs that are hard to reproduce, so we need to implement conditional and named modified store data, and since we’re going to distribute the action we’re going to pass in an action object, In addition, this object includes the state we want to modify and the named actionType we want to operate on.

 const dispatch = (action) = > {
     switch (action.type) {
    case 'ADD':
      return {
        ...state,
        num: state.num + 1,}case 'MINUS':
      return {
        ...state,
        num: state.num - 1,}case 'CHANGE_NUM':
      return {
        ...state,
        num: state.num + action.val,
      }
    // Return the default value if no method is matched
    default:
      return state
  }
 } 
Copy the code

From the code, there is no separate action here, so read on. The function is responsible for generating State. Because there is only one State object in the whole application, which contains all data, this State must be very large for a large application, resulting in a large Reducer function.

reducer

Reducer is a pure function that calculates the new state based on action and initState.

reducer(action,initState)
Copy the code

Forced to useactionThe benefit of describing all the changes is that you can clearly see what’s going on in your application. If something changes, you can see why. And finally, to get theactionandstateString it together and you have itreducer.reducer.js:

export default function reducer(action, state) {
  // Let the manager match what to do with the action. Type passed in
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        num: state.num + 1,}case 'MINUS':
      return {
        ...state,
        num: state.num - 1,}case 'CHANGE_NUM':
      return {
        ...state,
        num: state.num + action.val,
      }
    // Return the default value if no method is matched
    default:
      return state
  }
}
Copy the code

Test.js test results:

import createStore from './redux'
let initState = {
  num: 12,}const store = createStore(reducer, initState)
console.log(store.getState())
Copy the code

The output of the running code is normal.

Subscribe () even though it can store public states, store changes do not update the view directly, so we need to listen for store changes, using a very common design pattern — the observer pattern. Without further ado, let’s implement subscriber:

/** * store implements **@param {Function} Reducer management status update received: Action,state two parameters *@param {Object} InitState Specifies the initialization state. If num is not present, num defaults to NaN *@returns Return SUBSCRIBE, dispatch, getState * */
const createStore = (reducer, initState = { num: 10 }) = > {
  let state = initState
  let subscribes = [] // Store the observer
  // Add an observer
  const subscribe = (fn) = > {
    subscribes.push(fn)
  }
  // Inform all observers that the state is no longer passed, but the command to change the state (via a fixed command to tell the manager what to do)
  const dispatch = (action) = > {
    state = reducer(action, state) 
    // State changes, call (notify) all methods (observer)
    subscribes.forEach(fn= > fn())
  }
  // We need to add this method to get state
  const getState = () = > {
    return state
  }
  return {
    subscribe,
    dispatch,
    getState,
  }
}
export default createStore
Copy the code

Test.js tests the code

import createStore from './redux' let initState = { num: 12, } const store = createStore(reducer, InitState) store.subscribe(() => {let state = store.getState() console.log(' Received notification: Num)}) store.dispatch({type:'ADD'}) store.dispatch({type:'MINUS'}) store.dispatch({type:"CHANGENUM", val:20}) //Copy the code

Normal execution result:

action

An Action is a payload that transfers data from the application to the Store, either as a server response, user input, or other non-View data. It is the only source of store data. Typically, you will pass the action to the store via store.dispatch().

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.

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

The new action. Js

export const ADD = 'ADD'
export const MINUS = 'MINUS'
export const CHANGE_NUM = 'CHANGE_NUM'
/* * Action Creator generates Action */
export function add(text) {
  return { type: ADD, text }
}
export function minus(index) {
  return { type: MINUS, index }
}
export function changeNum(filter) {
  return { type: CHANGE_NUM, filter}}import { ADD,MINUS,CHANGE_NUM } from './action'
export default function reducer(action, state) {
  // Let the manager match what to do with the action. Type passed in
  switch (action.type) {
    case ADD:
      return {
        ...state,
        text: action.text,
        num: state.num + 1,}case MINUS:
      return {
        ...state,
        index: action.index,
        num: state.num - 1,}case CHANGE_NUM:
      return {
        ...state,
        val: action.val,
        num: state.num + action.val,
      }
    // Return the default value if no method is matched
    default:
      return state
  }
}
Copy the code

Test.js tests the application

import createStore from './redux'
import reducer from './reducer'
import { add,minus,changeNum } from './action'
let initState = {
  num: 12,}const store = createStore(reducer, initState)
store.subscribe(() = > {
  let state = store.getState()
  console.log(state) 
  console.log('Received notice:'.'state.num 'is updated with'+state.num)
})
store.dispatch(add('num+1'))
store.dispatch(minus(1))
store.dispatch(changeNum(20))
Copy the code

At this point we’ve basically implemented Redux, although it’s relatively crude and doesn’t affect our understanding of the idea.

Online source

conclusion

So let’s sort this outaction,store,reducer.viewsThe interaction process between them is as follows:

Redux itself has nothing to do with React. It’s just a data processing center. So how do they get connected?

Reference Documents:

  • Redux
  • React-redux