Redux Core Concepts

Redux Core Concepts

[[145606]]

http://gaearon.github.io/redux/index.html , the documentation is at http://rackt.github.io/redux/index.html . This article is not a translation of the official documentation. You can read this article before and after reading the official documentation to deepen the key concepts.

According to the source code convention of this project, examples are all written based on ES2015 syntax.

Redux is an application state management service. Although it is deeply influenced by Flux, its core concept is very simple, which is the Reduce in Map/Reduce.

Let's look at the usage of Array.prototype.reduce in Javascript:

  1. const initState = '' ;
  2. const actions = [ 'a' , 'b' , 'c' ];
  3. const newState = actions.reduce(
  4. ( (prevState, action) => prevState + action ),
  5. initState
  6. );

From the perspective of Redux, the state of the application is similar to initState and newState in the above function. After initState is given, as the value of the action is continuously passed to the calculation function, a new newState is obtained.

This calculation function is called Reducer, which is (prevState, action) => prevState + action in the above example.

Immutable State

Redux believes that in an application, all data that needs to be shared and accessed between application modules should be placed in the State object. This application module may refer to React Components, or it may be your own proxy module for accessing the AJAX API. There is no specific restriction on what it is. State saves data from different parts of the application in a "tree-like" manner. This data may come from network calls, local database queries, or even the temporary execution state of a current UI component (as long as it needs to be accessed by different modules), or even the current window size, etc.

Redux does not specify how to save the State, it may be a Javascript object, or an Immutable.js data structure. But one thing is that you'd better make sure that every node in the State is Immutable, so that when the State consumer determines whether the data has changed, they only need to perform a simple reference comparison, for example:

  1. newState.todos === prevState.todos

This avoids the traversal process of Deep Equal.

To ensure this, in your Reducer, updating the State member would be done like this:

  1. `let myStuff = [
  2. {name: 'henrik' }
  3. ]
  4.  
  5. myStuff = [...mystuff, {name: 'js lovin fool' ]`

myStuff is a brand new object.

If the object to be updated is an Object, then:

  1. let counters = {
  2. faves: 0 ,
  3. forward: 20 ,
  4. }
  5. // this creates a brand new copy overwriting just that key  
  6. counters = {...counters, faves: counters.faves + 1 }

Instead of:

  1. counters.faves = counters.faves + 1 }

Avoid in-place editing of objects. The same applies to arrays:

  1. let todos = [
  2. { id: 1 , text: 'have lunch' }
  3. ]
  4. todos = [...todos, { id: 2 , text: 'buy a cup of coffee' } ]

instead of:

  1. let todos = [
  2. { id: 1 , text: 'have lunch' }
  3. ]
  4. todos.push({ id: 2 , text: 'buy a cup of coffee' });

Following this approach, you can make your application state immutable without Immutable.js.

In Redux, the state can only be changed through actions. Reducer is a function that completes the state change according to the semantics of the action. Reducer execution is synchronous. Given initState and a series of actions, no matter when and how many times the Reducer is executed, the same newState should be obtained. This makes the state of your application log and replayable. This certainty greatly reduces the problem of complex state intrusion faced by front-end development. The certain state, coupled with Hot-Reloaidng and the corresponding Dev-Tool, greatly enhances the controllability of front-end applications.

State structure design

Redux (Flux) recommends that when saving State data, you should follow the paradigm as much as possible and avoid nested data structures. If nested objects appear, try to reference them by ID.

Assume that the data returned by the remote service is as follows:

  1. [{
  2. id: 1 ,
  3. title: 'Some Article' ,
  4. author:
  5. id: 1 ,
  6. name: 'Dan'  
  7. }
  8. }, {
  9. id: 2 ,
  10. title: 'Other Article' ,
  11. author:
  12. id: 1 ,
  13. name: 'Dan'  
  14. }
  15. }]

Then, it would be more efficient to convert it into the following form:

  1. {
  2. result: [ 1 , 2 ],
  3. entities:
  4. articles:
  5. 1 : {
  6. id: 1 ,
  7. title: 'Some Article' ,
  8. author: 1  
  9. },
  10. 2 : {
  11. id: 2 ,
  12. title: 'Other Article' ,
  13. author: 1  
  14. }
  15. },
  16. users:
  17. 1 : {
  18. id: 1 ,
  19. name: 'Dan'  
  20. }
  21. }
  22. }
  23. }

Normalized storage makes your data more consistent. In the above example, if users[1].name is updated, the author's name will also be updated in the component that displays articles.

In fact, this is the design principle of traditional relational databases. However, with the requirements for data distribution capabilities and horizontal scalability (giving up a certain degree of data consistency), the redundancy of server-side data is increasing. However, back to the client, since the total amount of data to be saved is not large (often just the cache of the user's most recently accessed data) and there is no requirement for distribution, normalized data storage is more advantageous. In addition to achieving consistency, it can also reduce storage space (storage space is more valuable on the client).

In addition, normalized storage also facilitates the localization of the Reducers discussed later, making it easier to split a large Reducer into a series of small Reducers.

Since the JSON data returned by the server (now a common method) is often redundant and not normalized, you may need some tools to help you convert it, such as: https://github.com/gaearon/normalizr, although it is often more efficient to control it yourself.

Reducer

Let's take a look at how Reducer works by familiarizing ourselves with todoApp:

  1. function todoAppReducer(state = initialState, action) {
  2. switch (action.type) {
  3. case SET_VISIBILITY_FILTER:
  4. return Object.assign({}, state, {
  5. visibilityFilter: action.filter
  6. });
  7. case ADD_TODO:
  8. return Object.assign({}, state, {
  9. todos: [...state.todos, {
  10. text: action.text,
  11. completed: false  
  12. }]
  13. });
  14. default :
  15. return state;
  16. }
  17. }

This example demonstrates how Reducers can update different State fields based on the action.type passed in.

If there are many action.type in the application, using a Reducer and a giant switch will obviously produce code that is difficult to maintain. In this case, a better approach is to combine small Reducers to generate a large Reducer, and each small Reducer is only responsible for processing a part of the fields of State. For example:

  1. import { combineReducers } from 'redux' ;
  2.  
  3. const todoAppReducer = combineReducers({
  4. visibilityFilter: visibilityFilterReducer
  5. todos: todosReducer
  6. });

visibilityFilterReducer and todosReducer are two small reducers, one of which is as follows:

  1. function visibilityFilterReducer(state = SHOW_ALL, action) {
  2. switch (action.type) {
  3. case SET_VISIBILITY_FILTER:
  4. return action.filter;
  5. default :
  6. return state;
  7. }
  8. }

The visibilityFilterReducer is only responsible for processing the state of the State.visibilityFilter field. This is achieved by passing the following form of parameters to combineReducers:

  1. {
  2. field1: reducerForField1,
  3. field2: reducerForField2
  4. }

filed1 and filed2 represent fields in State, reducerForField1 and reducerForField2 are the corresponding Reducers, each Reducer will only get the value of State.field1 or state.field2, and cannot see the content of other fields under State. The response return result will also be merged into the corresponding State field.

The premise of using combineReducers is that each combined Reducer is only related to a part of the State data, for example: the todos Reducer only consumes state.todos data and only produces state.todos data. If you need to consume other State fields, you still need to pass the entire State to a specific processing function in the big switch, for example:

  1. function todoAppReducer(state = initialState, action) {
  2. switch (action.type) {
  3. case FAVE_ALL_ITEMS:
  4. return Object.assign({}, state, faveThemAll(state));
  5. default :
  6. return state;
  7. }
  8. }

A Reducer can handle multiple action.types, and an action.type may be handled by multiple Reducers, which is a many-to-many relationship. The following Helper function can simplify the Reducer creation process:

  1. function createReducer(initialState, handlers) {
  2. return function reducer(state = initialState, action) {
  3. if (handlers.hasOwnProperty(action.type)) {
  4. return handlers[action.type](state, action);
  5. } else {
  6. return state;
  7. }
  8. }
  9. }
  10.  
  11. export const todosReducer = createReducer([], {
  12. [ActionTypes.ADD_TODO](state, action) {
  13. let text = action.text.trim();
  14. return [...state, text];
  15. }
  16. }

Store

In Redux, the Store object is used to maintain the application state. To construct a Store object, you only need to provide a Reducer function. As mentioned earlier, this Reducer function is responsible for interpreting the semantics of the Action object, thereby changing its internal state (that is, the state of the application).

The Store object therefore has two primary methods and one secondary method:

  1. store.getState(): Get the most recent internal state object.
  2. store.dispatch(action): sends an action object to the reducer.

A secondary method is: const unsure = store.subscribe(listener), which is used to subscribe to state changes. In React + Redux programs, it is not recommended to use store.subscribe. But if your application is based on the Observable mode, you can use this method to adapt it; for example, you can use this method to combine Redux with your FRP (Functional Reactive Programming) application.

The following example shows how a Store is created:

  1. import { combineReducers, createStore } from 'redux' ;
  2. import * as reducers from './reducers' ;
  3.  
  4. const todoAppReducer = combineReducers(reducers);
  5. const store = createStore(todoAppReducer); // Line 5  
  6.  
  7. store.dispatch({type: 'ADD_TODO' , text: 'Build Redux app' });

We can also specify an initial state for the Store when creatingStore, for example, replace the 5th line with:

  1. const store = createStore(reducers, window.STATE_FROM_SERVER);

In this example, the initial state comes from the STATE_FROM_SERVER property saved in the browser window object. This property is not a built-in property of the browser, but is embedded in the returned page file by our Web Server as an inline JavaScript. This is an implementation of a Universal (Isomorphic) Application. The client can directly obtain the initial state from the current page without initiating the first AJAX API request.

Action

In Redux, changing the state can only be done through actions. And each action must be a Javascript Plain Object, for example:

  1. {
  2. type: 'ADD_TODO' ,
  3. text: 'Build Redux app'  
  4. }

Redux requires that actions can be serialized so that application state saving, playback, and undo functions can be implemented. Therefore, actions cannot contain non-serializable fields such as function calls.

The action format is recommended and can contain the following fields:

  1. {
  2. type: 'ADD_TODO' ,
  3. payload: {
  4. text: 'Do something.'    
  5. },
  6. `meta: {}`
  7. }

If action is used to indicate an error condition, it may be:

  1. {
  2. type: 'ADD_TODO' ,
  3. payload: new Error(),
  4. error: true  
  5. }

type is a required attribute, and the others are optional. For complete recommendations, please refer to the Flux Standard Action (FSA) definition. Many third-party modules have been developed based on the FSA convention.

Action Creator

In fact, action objects are rarely created by declaring them directly each time, but more often by using a creation function. This function is called Action Creator, for example:

  1. `function addTodo(text) {
  2. return {
  3. type: ADD_TODO,
  4. text
  5. };
  6. }`

Action Creator looks simple, but it can become very flexible if combined with Middleware.

Middleware

If you have used Express, you will be familiar with its Middleware system. In the process of HTTP Request to Response, a series of Express Middlewares play different roles. Some Middleware are responsible for logging, some are responsible for converting internal exceptions to specific HTTP Status return values, and some are responsible for converting Query String to specific attributes of the request object.

The design motivation of Redux Middleware is indeed from Express. Its main mechanism is to establish a chain of store.dispatch, each middleware is a link in the chain, and the incoming action object is processed step by step until it is finally spit out as a Javascript Plain Object. Let's take a look at an example:

  1. import { createStore, combineReducers, applyMiddleware } from 'redux' ;
  2.  
  3. // applyMiddleware takes createStore() and returns// a function with a compatible API.  
  4. let createStoreWithMiddleware = applyMiddleware(
  5. logger,
  6. crashReporter
  7. )(createStore);
  8.  
  9. // Use it like you would use createStore()let todoApp = combineReducers(reducers);  
  10. let store = createStoreWithMiddleware(todoApp);

In this example, the two Middlewares, logger and crashReporter, respectively complete the functions of recording action logs and recording action processing exceptions.

The logger code is as follows:

  1. ` // Logs all actions and states after they are dispatched.  
  2. const logger = store => next => action => {
  3. console.log( 'dispatching' , action);
  4. let result = next(action);
  5. console.log( 'next state' , store.getState());
  6. return result;
  7. };`

Longer is a function after currying (this is a basic concept of functional programming. Compared with Flux, Redux uses a lot of functional programming). Next is the dispatch function returned by the next Middleware (analysis will be later). For a Middleware, with the store object, you can get the most recent application state through store.getState() for decision-making, and with next, you can control the transfer process.

ES6's Fat Arrow Function syntax (logger = store => next => action =>) makes the original function return function syntax more concise (I ❤️☕️!).

For industrialized logger implementations, see https://github.com/fcomb/redux-logger and https://github.com/fcomb/redux-diff-logger . The same author wrote both, and the latter supports State difference display.

vanilla promise

Middleware can also be used to convert incoming actions. In the following example, the incoming action is a Promise (which obviously does not meet the requirement that the action must be a Javascript Plain Object), so it needs to be converted:

  1. ` /**
  2. * Lets you dispatch promises in addition to actions.
  3. * If the promise is resolved, its result will be dispatched as an action.
  4. * The promise is returned from `dispatch` so the caller may handle rejection.
  5. */  
  6. const vanillaPromise = store => next => action => {
  7. if (typeof action.then !== 'function' ) {
  8. return next(action);
  9. }
  10. ` // the action is a promise`  
  11. return Promise.resolve(action).then(store.dispatch);
  12. };`

In this example, if the passed action is a Promise (that is, it contains a .then function, which is just a rough judgment), then the Promise is executed, and when the Promise is executed successfully, the result is passed directly to store.dispatch (no longer passing through the subsequent Middlewares chain). Of course, we must ensure that the execution result of the Promise returns a Javascript Plain Object.

This usage may not be common, but from this example we can understand that we can define the semantics of our own action, and then parse it through the corresponding middleware to generate specific execution logic to generate the final action object. This execution process may be synchronous or asynchronous.

From this example, you may also find that if we also load the logger Middleware, the logger can know that the Promise action has entered the dispatch function chain, but it has no chance to know what happened after the final Promise execution succeeded/failed, because regardless of whether the Promise is executed successfully or not, the original store.dispatch will be called directly without going through the dispatch function chain created by Middlewares.

For full support for Promise, see: https://github.com/acdlite/redux-promise.

Scheduled Dispatch

The following example is a bit more complex and demonstrates how to delay the dispatch of an action.

  1. ` /**
  2. * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds.
  3. * Makes `dispatch` return a function to cancel the interval in this case.
  4. */  
  5. const timeoutScheduler = store => next => action => {
  6. if (!action.meta || !action.meta.delay) {
  7. return next(action);
  8. }
  9.  
  10. let intervalId = setTimeout(
  11. () => next(action),
  12. action.meta.delay
  13. );
  14.  
  15. return function cancel() {
  16. clearInterval(intervalId);
  17. };
  18. };`

In this example, if the timeoutScheduler Middleware finds that the incoming action parameter has the meta.delay field, it will consider that the action needs to be sent with a delay. When the declared delay time (meta.delay) is reached, the action object will be sent to the dispatch method of the next Middleware.

The following Middleware is very simple, but provides very flexible usage.

Thunk

If you don't understand the concept of Thunk, you can read http://www.ruanyifeng.com/blog/2015/05/thunk.html first.

The implementation of thunk Middleware is very simple:

  1. ` const thunk = store => next => action =>
  2. typeof action === 'function' ?
  3. action(store.dispatch, store.getState):
  4. next(action);`

The following example loads thunk and dispatches a Thunk function as an action.

  1. const createStoreWithMiddleware = applyMiddleware(
  2. logger,
  3. thunk
  4. timeoutScheduler
  5. )(createStore);
  6. const store = createStoreWithMiddleware(combineReducers(reducers));
  7.  
  8. function addFave(tweetId) {
  9. return (dispatch, getState) => {
  10. if (getState.tweets[tweetId] && getState.tweets[tweetId].faved)
  11. return ;
  12.  
  13. dispatch({type: IS_LOADING});
  14. // Yay, that could be sync or async dispatching  
  15. remote.addFave(tweetId).then(
  16. (res) => { dispatch({type: ADD_FAVE_SUCCEED}) },
  17. (err) => { dispatch({type: ADD_FAVE_FAILED, err: err}) },
  18. };
  19. }
  20.  
  21. store.dispatch(addFave());

This example demonstrates the process of generating action objects related to "collecting" a microblog. As an action creator, addFave does not return a Javascript plain object, but a Thunk function that receives dispatch and getState as parameters.

When the thunk Middleware finds that the incoming action is such a Thunk function, it will equip the function with the dispatch and getState parameters to allow the Thunk function to be executed. Otherwise, it will call next(action) to give the subsequent Middleware a chance to dispatch.

In the Thunk function, it first determines whether the microblog in the current application state has been fave. If not, the remote method is called.

If a remote method is called, the IS_LOADING action is first issued to tell the reducer that a remote call has been initiated. This allows the reducer to update the corresponding state property. In this way, if there is a UI Component that cares about this state, it can update the interface accordingly.

If the remote method is successfully called, an action object representing success ({type: ADD_FAVE_SUCCEED}) will be dispatched. Otherwise, an action object representing failure ({type: ADD_FAVE_FAILED, err: err}) will be generated. In any case, the action object finally received by the reducer must be this kind of Javascript Plain Object.

When the Thunk Middleware processes the Thunk function type action, if other Middlewares are configured, they will be skipped without a chance to execute.

For example, if our Middlewares are configured as applyMiddleware(logger, thunk, timeoutScheduler), when the action is a Thunk function, this action will not have a chance to be executed by the timeoutSchedulerMiddleware, while the logger Middleware will have a chance to be executed before the thunk Middleware.

applyMiddleware

The tool function for assembling Middlewares is applyMiddleware, and the simulation implementation of this function is as follows:

  1. function applyMiddleware(store, middlewares) {
  2. middlewares = middlewares.slice();
  3. middlewares.reverse();
  4.  
  5. let next = store.dispatch;
  6. middlewares.forEach(middleware =>
  7. next = middleware(store)(next)
  8. );
  9.  
  10. return Object.assign({}, store, { dispatch: next });
  11. }

Combined with the writing of Middleware:

  1. const logger = store => next => action => {
  2. console.log( 'dispatching' , action);
  3. let result = next(action);
  4. console.log( 'next state' , store.getState());
  5. return result;
  6. };

We can see that after passing store and next to the Middleware, a new dispatch method is returned. The next parameter passed in is the dispatch function returned by the Middleware before. In this way, before the action is actually passed in, we get a dispatch function that is connected in series, which is used to replace the original store.dispatch method (through Object.assign(...)). The purpose of the Redux Middleware mechanism is to change the behavior of store.dispatch in the form of a plug-in, so that it can handle different types of action inputs and obtain the final action object in the form of a Javascript Plain Object.

Each Middleware can get:

  1. The original store object (the dispatch property is still the original one), so you can get the latest state through store.getState, and publish action objects directly through the original dispatch object, skipping other Middleware dispatch methods (next). The above vanillaPromise demonstrates this usage.
  2. next method: The dispatch method returned by the previous Middleware. The current Middleware can decide whether to call the next method and what parameters to pass in based on its own judgment and processing results of the action.

Take the statement newStore = applyMiddleware(logger, thunk, timeoutScheduler)(store)) as an example. The next parameter obtained by timeoutScheduler is the original store.dispatch method; thunk has the dispatch method returned by timeoutScheduler, and logger has the dispatch method returned by thunk. Finally, the dispatch method of the newly generated newStore is the one returned by logger. Therefore, the actual order of action flow is first to the dispatch method returned by logger, then to the dispatch method returned by thunk, and finally to the dispatch method returned by timeoutScheduler.

It should be noted that because the logger is the first in the dispatch chain, it can get every action object that enters. However, because other Middleware may call dispatch asynchronously (asynchronously calling the dispatch method returned by the previous Middleware or the original store.dispatch), the logger may not necessarily have the opportunity to know how the action is ultimately delivered.

Middleware can be used in many ways. The following document lists the principles of Middleware and seven types of Middlewares: http://rackt.github.io/redux/docs/advanced/Middleware.html.

Store/reducer is the core logic of Redux, while Middleware is an extension of it, which is only responsible for the generation of action objects. However, since Redux has very strict restrictions on the core part (keeping the core concept simple): for example, reducer must be synchronous, the requirements brought by actual engineering needs are pushed to the Dispatch/Middleware part, and the usage mentioned in the official document plays a guiding role in "best practices".

Higher-Order Store

Middleware is an extension mechanism for the store.dispatch method. But sometimes the entire store object needs to be extended, which introduces the concept of Higher-Order Store.

This concept is similar to the Higher-Order Component concept of React. https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md , which provides a function that accepts a store object as an input parameter and generates a new store object as a return value.

  1. createStore => createStore'

Redux recommends that you use Higher-Order Store only when Middleware cannot meet the expansion requirements. The redux-devtools that comes with Redux is an example.

Binding To React (React-Native)

The above section introduces the core components and data flow of Redux, which can be reviewed through the following figure:

                                                                                      ┌──────────────┐
                        ┌─────────────┐ ┌──▶│ subReducer 1 │
                   ┌───▶│Middleware 1 │ │ └───────────────┘
                   │ └─────────────┘ │ │       
                   │ │ │ ▼       
┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐
│ action' │────┘ ▼ ┌──▶│store.dispatch │───▶│ reducer │───┘ │ subReducer m │
└─────────────┘ ┌─────────────┐ │ └──────────────┘ └───────────┘ └───────────────┘
                        │Middleware n │ │ │       
                        └─────────────┘ │ │       
                               │ │ ▼       
                               │ │ ┌──────────────┐
                               └──────────┘ │ state │
                               plain action └───────────────┘

Redux solves the problem of application state storage and how to change it. As for how to use it, it depends on other modules. For how to use Redux in React or React-Native, you need to refer to react-redux.

react-redux is a binding for React Components to use Redux. Let's analyze a specific example.

  1. import { Component } from 'react' ;
  2.  
  3. export default   class Counter extends Component {
  4. render() {
  5. return (
  6. <button onClick={ this .props.onIncrement}>
  7. { this .props.value}
  8. </button>
  9. );
  10. }
  11. }

This is a React Component that displays a button. When this button is pressed, this.props.onIncrement is called. The specific content of onIncrement is shown in the example below. It works as follows: Each time onIncrement is called, it dispatches the {type: INCREMENT} Action object to update the Store/State.

In react-redux, such a component is called a "Dumb" component, which is completely ignorant of Redux. It only knows how to get the required Action Creator from this.props and understands its semantics, and calls the method when appropriate. The external data that the "Dumb" component needs to display also comes from this.props.

How to prepare this.props for "Dumb" Component? The connect function provided by react-redux can help you do this:

  1. import { Component } from 'react' ;
  2. import { connect } from 'react-redux' ;
  3.  
  4. import Counter from '../components/Counter' ;
  5. import { increment } from '../actionsCreators' ;
  6.  
  7. // Which part of the Redux global state does our component want to receive as props?  
  8. function mapStateToProps(state) {
  9. return {
  10. value: state.counter
  11. };
  12. }
  13.  
  14. // Which action creators does it want to receive by props?  
  15. function mapDispatchToProps(dispatch) {
  16. return {
  17. onIncrement: () => dispatch(increment())
  18. };
  19. }
  20.  
  21. export default connect( // Line 20  
  22. mapStateToProps,
  23. mapDispatchToProps
  24. )(Counter);

The connect in line 20 maps some property of state to the this.props property of Counter Component, and also passes the dispatch method for a specific Action Creator to this.props. In this way, the Counter Component can complete action dispatching and application state acquisition only through this.props.

If the connect function omits the second parameter, connect(mapStateToProps)(Counter), then the dispatch method will be passed directly to this.props. This is not a recommended approach because it means that Counter needs to understand the functionality and semantics of dispatch.

Nesting of Components

You can call connect at any level of your component tree to bind state and dispatch methods for lower-level components. However, calling connect only at your top-level component is the preferred method.

Provider Component

The above example is actually not executable, because the connect function does not actually know where the Redux store object is. So we need a mechanism to let connect know to get the store object from you, which is set by the Provider Component, which is also a tool component provided by react-redux.

  1. React.render(
  2. <Provider store={store}>
  3. {() => <MyRootComponent />}
  4. </Provider>,
  5. rootEl
  6. );

Provider Component should be the root component of your React Components tree. Due to a problem in React 0.13, the child component of Provider Component must be a function, which will be fixed in React 0.14.

The cooperation between Provider Component and connect function makes React Component completely unaware of Redux and only uses React's own mechanism to obtain and maintain the application status.

selector

In the above example, the mapStateToProps function in connect(mapStateToProps,mapDispatchToProps)(Counter) specifies which Store/State properties are mapped to this.props of React Component by returning a mapping object. This method is called a selector. The role of the selector is to construct a state view that suits the needs of React Components. The introduction of the selector reduces the dependence of React Component on the Store/State data structure, which is conducive to code decoupling. At the same time, since the implementation of the selector is completely a custom function, it is also flexible enough (for example, filtering and aggregating the original state data).

The reselect project provides a selector with cache function. If the Store/State and the parameters used to construct the view have not changed, the data obtained by the Component each time will come from the result of the last call/calculation. Thanks to the immutable nature of the Store/State, the detection of state changes is very efficient.

Summarize

  1. Redux is not directly related to React, it is aimed at application state management.
  2. The core concept is Reduce in Map/Reduce. The execution of Reducer is synchronous, and the generated State is Immutable.
  3. Changing the State can only be done by dispatching actions to the Reducer.
  4. Different fields of State can be maintained separately by different Reducers. combineReducers is responsible for combining these Reducers, provided that each Reducer can only maintain the fields it cares about.
  5. Action objects can only be Javascript Plain Objects, but by loading middleware on the store, action objects can be generated very flexibly, and a lot of control logic can be implemented using them as a central point, such as Log, Undo, ErrorHandler, etc.
  6. Redux only focuses on maintaining application status. Reducer and dispatch/middleware are two common extension points. Higher-order Store is only used when all Store functions need to be extended.
  7. react-redux is Redux's Binding for React/React-Native, and connect/selector is the extension point.
  8. Redux is a typical product of functional programming. Understanding functional programming will help you understand its implementation principles, although you don't need to understand many functional programming concepts to use it. Compared with Flux, Redux has a more concise concept, stricter conventions, more certain states, and more flexible extensions.
  9. A lot of reference is available at https://github.com/xgrommx/awesome-redux

Other references

A comprehensive reference for all things Redux.

https://github.com/xgrommx/awesome-redux

<<:  Android M full name Android Marshmallow

>>:  APP promotion: digging into those niche promotion channels

Recommend

Template for planning fission activities!

1. #Split the wheel lottery game and get a gift b...

Xiaohongshu promotion strategy, big data + 3 major strategies!

With the development of information technology, t...

Help! How can the virus he draws be so beautiful!!!

On Earth, the interconnected roads, the densely p...

Do you know about electromyography, which allows muscles to "speak"?

Everyone is familiar with electrocardiogram and e...

What imprints of traditional culture are there in aerospace engineering?

On July 12, 2024, Cui Wanzhao, a researcher at th...