TL; DR

Through this article, you will know:

  1. The background of Redux
  2. What problems Redux simplifies
  3. How does Redux solve Flux.waitFor(id[])function
  4. An implementation of Middleware and enhancer in the Redux plug-in extension
  5. Redux plugin idea

background

In 2014, Facebook’s front-end infrastructure team open-source Flux and React. Redux is one of a wave of Flux related data management libraries that have sprung up in the React data management ecosystem after Facebook open-source Flux. In 2015, Dan proposed the Redux state management library in his React-Europe 2015 speech to solve the problems existing in Flux.

For an in-depth look at the problems Flux solves, see Flux in my React Data Management article.

In Dan’s speech, he started talking about Redux in the 11th minute. The first 11 minutes were about the Hot Reloading mechanism of React.

Redux solves the problem

When Flux is selected as the state management library, the code is as follows:

Dan then made a series of adjustments and cuts to the above code, a process similar to code refactoring, in order to make the code more concise.

  1. The Store byexportExpose and let Store managers coordinate which stores should be used. Instead of passing through a Store fileAppDispatcher.registerRegister. (The essence is to separate the declaration and use of the Store.)
  2. will_todos.push(text)Instead of_todos = [..._todos, text]. Instead of modifying objects directly, you create new objects using ES7’s Spread syntax. The reason: you can determine whether the data has changed by referring to the same data, which makes it more efficient and less complicated to write.
  3. willgetAll()As a separate functionexportGo out and even eventually delete this function in the Store file. The reason is:getAll()As a data accessor, it should be on top of the Store. Because data accessors sometimes need to combine data from multiple stores, they should be maintained in another level, not the same level as the data.
  4. deleteEventEmitter.prototype. Flux usingEventEmitterThe purpose is to callTodoStore.emitChange()Notify subscribers that the Store has changed. Since the second point is no longer needed, it is possible to determine whether the Store has changed by comparing references to the data in the StoreemitChange().
  5. will_todoModule variables are deleted and data is passed in by the manager via parameters. In the first step, the Store manager is required to coordinate all stores, so the data in the Store should also be uploaded by the manager, and the Store is responsible for calculating the new data and then telling the new data to the manager.

The modified code is the Reducer of Redux, which follows the Reducer idea of Flux: Store is modified according to Action.

So Redux builds on Flux and simplifies the Store code in Flux. I think the most obvious simplification is that you don’t need to use emitChange() to notify subscribers.

Redux and one-way data flow

The most important concept in Flux is one-way data flow, so how does Redux embody this?

In the source code, as long as the Reducer function triggered by the Dispatch Action is executing, no new Action can be dispatched by dispatch again.

How does Redux solve the dependency update problem

What is the dependency update problem

Suppose we have two stores, the ProductListStore and the TotalPriceStore. When the user adds an item (defining Action as AddProductAction), ProductListStore is updated, and then TotalPriceStore calculates the final price based on all items in ProductListStore.

In the above scenario, both ProductListStore and TotalPriceStore handle AddProductAction, but TotalPriceStore needs to wait for ProductListStore to finish processing. This is the dependency update problem. Flux resolves the dependency update problem of multiple stores on the same Action through the.waitfor (id[]) interface.

Derived data

In the previous scenario, because the total price could be calculated from the list of goods, you could design the total price as Derived State. Can all dependent updates be converted to derived data?

The answer is no.

For example, if a country has only one city, the city can be calculated automatically after the country is selected. But after a country has more than one city, the city can be selected by the user, then the city cannot be derived from the country, if the value of the city is derived from the value of the country, the value of the city will never change. Although cities are not derived data from countries, they have a dependency update problem. When the user selects a country, the selected city needs to change accordingly, with the following code (from Flux/Doc/Dispatcher) :

CityStore.dispatchToken = flightDispatcher.register(function(payload) {
  if (payload.actionType === "country-update") {
    // `CountryStore.country` may not be updated.
    flightDispatcher.waitFor([CountryStore.dispatchToken])
    // `CountryStore.country` is now guaranteed to be updated.

    // Select the default city for the new country
    CityStore.city = getDefaultCityForCountry(CountryStore.country)
  }
})
Copy the code

Dependency update versus derived data

Derived data means that its value can be computed from another value, but it cannot be computed if the derived value itself can be modified independently. So if a value can be modified independently, it cannot be designed to be derived. This is also a principle of what data can be derived.

The essence of dependent updates is that both StoreA and StoreB need to process an Action, and StoreB depends on the latest value after StoreA processes it. Can we bypass dependency updates by encapsulating StoreA’s processing behavior as a function and rerunning it in StoreB?

The answer is no. Because when you re-run StoreA’s handler in StoreB, if StoreA has been updated, it will be wrong to execute it again.

Alternatively, we can calculate the data that StoreB depends on before the dispatch Action (in which case StoreA must be an old value), and send the calculated result to StoreB in a payload. In the example above, changing countrystore. country to paypay. country avoids relying on updates. However, the disadvantages are obvious: there is a waste of performance, the function is required to have no side effects, and there is a cost of modification (the modification is spread all over the place).

How does Redux solve the dependency update problem

Redux addresses dependency updates in the following ways:

  1. Recommend it. If the state could be designed as derived data, there would be no dependency updates.
  2. recommended. inredux-thunkDispatch two actions, one for updateStoreA, and the other ActionStoreATo modify the latest value ofStoreB. The downside of this approach is that you need to dispatch two actions and cannot update them in oneStoreAStoreB.
  3. Before dispatch Action, run the Reducer of StoreA to calculate the new value, and then put the new value intopayloadOn, and finally onStoreBThe new value is available from the Reducer.
  4. StoreBThe third parameter is accepted by the Reducer and passed to it in rootReducer. referenceBeyond combineReducersBut this article focuses on the issue of sharing data rather than relying on updates. Shared data requires the data to be old, and dependent updates require the data to be new.

Middleware and enhancer implementations in Redux

Redux has two extension mechanisms, enhancer and Middleware. Since the last value returned by Middleware is enhancer, we’ll start with enhancer.

enhancer

Enhancer is the last parameter to createStore, simplified as follows:

function createStore(reducer, preloadedState, enhancer) {
  if (enhancer) {
    // Pass in the current createStore
    return enhancer(createStore)(reducer, preloadedState)
  }

  // Raw processing logic
}
Copy the code

Enhancer is essentially a reduce function that takes createStore as an argument and returns a createStore.

middleware

The middleware return value is an enhancer that only modifies store.dispatch. From the source, you can see that Middleware first accepts the middlewareAPI, whose return value is used by compose as an enhanced Dispatch method.

The way to enhance Dispatch is essentially a Reduce function that takes a Dispatch method as an argument and returns a Dispatch method.

It’s interesting to pass a new dispatch in the Middleware source code. Its existence eliminates the need for the user to pass the resulting store to the third party plug-in, which is an advantage, but the disadvantage is that the dispatch here is not the same as the dispatch of the final store. Fortunately, this disadvantage is not as important as the advantage. Looking at the online Demo, the net effect is that you’ll see three unequal, unintelligible dispatches.

Redux plugin idea: Reduce and compose

As you can see from the enhancer and Middleware implementations, the idea behind Redux’s plug-in is to enhance the API through the Reduce method. The reduce approach to enhancing Dispatch is to pass in the Dispatch and return a new Dispatch function. The reduce method for enhancing createStore is to pass in a createStore and return a new createStore.

The compose method combines multiple enhancements into one. Redux’s Compose is executed from right to left, as shown in the Compose source code.

conclusion

Redux is a state management library implemented by Dan a year after Facebook opened Flux open source. It takes the idea of Flux and simplifies Store creation and the way to determine when data is updated.

While Redux lacks Flux’s waitFor API compared to Flux, this article explains in detail what is the difference between dependent updates and derived data, and how dependent updates can be resolved in Redux.

Finally, the realization principle of Middleware and enhancer in Redux is analyzed, and the idea of Redux plug-in is to use reduce method to extend THE API.