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:
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:
This avoids the traversal process of Deep Equal. To ensure this, in your Reducer, updating the State member would be done like this:
myStuff is a brand new object. If the object to be updated is an Object, then:
Instead of:
Avoid in-place editing of objects. The same applies to arrays:
instead of:
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:
Then, it would be more efficient to convert it into the following form:
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:
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:
visibilityFilterReducer and todosReducer are two small reducers, one of which is as follows:
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:
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:
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:
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:
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:
We can also specify an initial state for the Store when creatingStore, for example, replace the 5th line with:
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:
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:
If action is used to indicate an error condition, it may be:
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:
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:
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:
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:
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.
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:
The following example loads thunk and dispatches a Thunk function as an action.
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:
Combined with the writing of Middleware:
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:
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.
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.
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:
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.
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
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
As of now, the number of mini programs has reache...
This article shares with you Tencent’s July adver...
Written by: Zhu Hengheng Editor: Wang Haha Layout...
In the past few years, the SUV market has grown r...
1. #Split the wheel lottery game and get a gift b...
Author: Global Science We are used to the fact th...
With the development of information technology, t...
On Earth, the interconnected roads, the densely p...
Everyone is familiar with electrocardiogram and e...
If Apple and Google hadn't added an app store...
Eternal feminine, lead us upward. Das ewige weibl...
According to statistics, the number of WeChat min...
Even good wine needs no bush. In this era of info...
On July 12, 2024, Cui Wanzhao, a researcher at th...
On June 8, the China Passenger Car Association re...