Redux released a few days ago (2018.04.18), 6 commits were subscribed to master. From its inception to its current version 4.0, Redux has maintained a smooth transition of usage levels. React was also recently upgraded from version 15 to version 16, allowing developers to make painless updates without making major changes. But there’s a lot of interesting design behind the release iteration that’s worth knowing. The same is true with the Redux upgrade.

This article will start from the version of the upgrade, from the source code changes, analysis. Through the following content, I believe that readers can have a deeper understanding of the basic level of JavaScript.

This article supports front-end beginners to learn, and is more suitable for those with Redux source code reading experience, the core source code will not be repeated analysis, more will focus on upgrading changes.

Change point overview

There are 22 major changes, most notably TypeScript usage, CommonJS and ES builds, and errors with state. We don’t bother with engineering and configuration changes. Mainly from the details of the code, starting from the basics, focusing on the analysis of the following changes:

  • Middleware API Dispatch parameter processing;
  • ApplyMiddleware changes;
  • BindActionCreators dealt with this transparently;
  • In the dispatching dispatching, the state is frozen;
  • Plain Object type judgment;

Without further ado, let’s cut to the chase.

Handle the applyMiddleware parameter

This change was proposed by Asvarox. Readers familiar with the design of applymiddleware.js in Redux’s source code will be familiar with the middlewareAPI: For each middleware, part of the Store, the middlewareAPI, can be sensed. Here’s a quick expansion:

const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(... chain)(store.dispatch)Copy the code

Create a middleware store:

let newStore = applyMiddleware(mid1, mid2, mid3, ...) (createStore)(reducer, null);Copy the code

Let’s see, applyMiddleware is a tertiary curried function. It takes three arguments in succession, the first being the middlewares array, [MID1, MID2, MID3… , the second is Redux native createStore, and the last is reducer;

ApplyMiddleware creates a store using createStore and Reducer, and the Store’s getState and Dispatch methods are directly and indirectly assigned to the middlewareAPI variable, respectively. The Middlewares array uses the Map method to make each middleware run separately with the middlewareAPI parameter. Compose then assembles all the anonymous functions in the chain, [f1, f2,… fx,… fn], into a new function. When the new dispatch is executed, [F1, F2… FX… fn] will be executed from right to left. The above explanation is modified from the Pure Render column.

Ok, with a brief explanation of the middleware mechanism, let’s take a look at this change. The story began when Asvarox designed a custom middleware that received a dispatch that required two parameters. His masterpiece looks something like this:

const middleware = ({ dispatch }) => next => (actionCreator, args) => dispatch(actionCreator(... args));Copy the code

Compare the routine of traditional writing middleware:

const middleware = store => next => action => {... }Copy the code

We can clearly see the problem with this way of writing: the ARgs behind the actionCreator parameter will be missing from the original Redux source code. So his proposed changes are:

const middlewareAPI = { getState: store.getState, - dispatch: (action) => dispatch(action) + dispatch: (... args) => dispatch(... args) }Copy the code

If you’re wondering why he designed his middleware this way, refer to Issue #2501. Personally, I think for demand, his “strange” way can be avoided by other means; However, for the Redux library, it is more appropriate to expand the middleWareapi.dispatch parameter.

We have touched on this change and will not go to the last point. Should learn: es6-based indefinite parameter and expansion operators clever use. For all the talk and all the talk, when it comes to actually developing programs, we still need to pay attention and form good habits.

Based on this, the same changes are also made:

export default function applyMiddleware(... middlewares) { - return (createStore) => (reducer, preloadedState, enhancer) => { - const store = createStore(reducer, preloadedState, enhancer) + return (createStore) => (... args) => { + const store = createStore(... args) let dispatch = store.dispatch let chain = []Copy the code

This change was proposed by jimbolla.

BindActionCreators has done this transparently

The Creators of the bindActionCreators in Redux enable Dispatch to wrap up the action. In this way, the method created through bindActionCreators can call Dispatch (Action) directly. In the action.js file, we defined two Action creators:

function action1(){
  return {
   type:'type1'
  }
}
function action2(){
  return {
   type:'type2'
  }
}
Copy the code

In another file, somecomponent.js, we can directly use:

import { bindActionCreators } from 'redux'; import * as oldActionCreator from './action.js' class C1 extends Component { constructor(props) { super(props); const {dispatch} = props; this.boundActionCreators = bindActionCreators(oldActionCreator, dispatch); } componentDidMount() {// componentDidMount() {// Let {dispatch} = this. Props; let action = TodoActionCreators.addTodo('Use Redux'); dispatch(action); } render() { // ... let { dispatch } = this.props; let newAction = bindActionCreators(oldActionCreator, dispatch) return <Child {... newAction}></child> } }Copy the code

Thus, calling newAction.action1 directly in the Child component is equivalent to calling Dispatch (action1). The advantage of this is that you can distribute actions without a store or dispatch component.

There aren’t many uses of this API, at least not for me. So here’s a brief introduction. Experienced developers must have no difficulty guessing what the bindActionCreators source code did, along with this change:

function bindActionCreator(actionCreator, dispatch) { - return (... args) => dispatch(actionCreator(... args)) + return function() { return dispatch(actionCreator.apply(this, arguments)) } }Copy the code

Let’s look at this change and use the apply method on actionCreator to explicitly bind this. So what’s the point of this?

As an example, imagine applying the This binding to the original actionCreator and using the bindActionCreators method:

const uniqueThis = {};
function actionCreator() {
  return { type: 'UNKNOWN_ACTION', this: this, args: [...arguments] }
};
const action = actionCreator.apply(uniqueThis,argArray);
const boundActionCreator = bindActionCreators(actionCreator, store.dispatch);
const boundAction = boundActionCreator.apply(uniqueThis,argArray);
Copy the code

We should expect boundAction to be consistent with action; Boundaction. this and uniqueThis are the same as action.this. With such expectations, such a change is surely necessary.

Freeze state

Dan Abramov believes that using the getState() and subscribe() methods in reducer is an anti-pattern. The call to store.getState makes the reducer impure. In fact, the dispatch method was disabled in the Reducer execution in the original reducer. The source code is as follows:

  function dispatch(action) {
    // ...

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }
Copy the code

Meanwhile, this modification applies the same freeze to the getState and SUBSCRIBE and unsubscribe methods:

 if (isDispatching) {
  throw new Error(
    'You may not call store.subscribe() while the reducer is executing. ' +
      'If you would like to be notified after the store has been updated, subscribe from a ' +
      'component and invoke store.getState() in the callback to access the latest state. ' +
      'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
  )
}
Copy the code

In my opinion, there is no debate about this approach. It is reasonable to throw an explicit exception unintentionally.

Plain Object type

Plain Object is a very interesting concept. This change resulted in a heated discussion around determining the performance of the Plain Object. /utils/isPlainObject: /utils/isPlainObject:

- import isPlainObject from 'lodash/isPlainObject';
+ import isPlainObject from './utils/isPlainObject'
Copy the code

Simply put, Plain Object:

Refers to an Object defined either as a literal or as a new Object().

This time Redux uses the following code to determine:

export default function isPlainObject(obj) { if (typeof obj ! == 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) ! == null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }Copy the code

If the above code does not make sense to the reader, then the knowledge of prototypes and prototype chains is needed. If obj’s prototype chain has only one level, return true. If you still don’t understand, consider the following sample code:

Function Foo() {} // obj is not a plain object var obj = new Foo(); console.log(typeof obj, obj ! == null); let proto = obj while (Object.getPrototypeOf(proto) ! == null) { proto = Object.getPrototypeOf(proto) } // false var isPlain = Object.getPrototypeOf(obj) === proto; console.log(isPlain);Copy the code

Loadash is implemented as follows:

function isPlainObject(value) { if (! isObjectLike(value) || baseGetTag(value) ! = '[object Object]') { return false } if (Object.getPrototypeOf(value) === null) { return true } let proto = value while  (Object.getPrototypeOf(proto) ! == null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(value) === proto } export default isPlainObjectCopy the code

IsObjectLike source code:

function isObjectLike(value) { return typeof value == 'object' && value ! == null }Copy the code

BaseGetTag source code:

const objectProto = Object.prototype const hasOwnProperty = objectProto.hasOwnProperty const toString = objectProto.toString const symToStringTag = typeof Symbol ! = 'undefined' ? Symbol.toStringTag : undefined function baseGetTag(value) { if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]' } if (! (symToStringTag && symToStringTag in Object(value))) { return toString.call(value) } const isOwn = hasOwnProperty.call(value, symToStringTag) const tag = value[symToStringTag] let unmasked = false try { value[symToStringTag] = undefined unmasked = true } catch (e) {} const result = toString.call(value) if (unmasked) { if (isOwn) { value[symToStringTag] = tag } else { delete value[symToStringTag] } } return result }Copy the code

According to the comparison results given by Timdorr, in the dispatch method:

Master: ms nodash 4690.358:82.821 msCopy the code

This set of benchmarks has certainly led to some discussion, including Dan Abramov. The author does not express any opinion on this, interested students can do their own research. In terms of results, some reliance on LoDash has been removed, and performance has been enhanced.

Outlook and Summary

Redux’s development is closely related to React. React’s new version has become extremely popular. In particular, new apis such as Context, which some developers believe will gradually replace Redux, are emerging.

The author believes that data state management is always an extremely important topic for React application development. But React Context and Redux aren’t exactly opposites.

First, React’s context feature does not reduce template code for large data applications. The one-to-one correspondence between Provider and Consumer means that both the Provider and Consumer must come from the same react. createContext call (you can hack around this limitation). It seems that the React team’s direction for this feature is mainly reflected in small state management. If you need more flexibility and direct operations, Redux might be a better choice.

Second, Redux’s rich ecosystem and middleware and other mechanisms make it largely irreplaceable. After all, for projects that already use Redux, the migration costs will be significant. At the very least, React will need to be upgraded in development to support the new context.

Finally, as a “publish and subscribe system”, Redux can completely exist independently from React, and such genes also determine the characteristics of the following day that are different from the React context itself.

In my opinion, the React Context update is a long-term complement and improvement to React’s “weak board”, and will probably be polished and adjusted in the future. Redux will also go through a series of iterations, but as with this release, it will be more stable and more detailed.

To say the least, React Context does have a lot to do with Redux. Any library or framework has its drawbacks, and so does Redux. We can use the new React Context to circumvent some of Redux’s disadvantages at the usage level, emulating everything Redux can do. As Didierfranc’s React-Waterfall, domestic @ Founder’s Rectx, are based on the new React Context solution.

Finally, I quite agree with @ cheng says, choose what kind of tool is never decided to a development team, the key to success, according to the business scenario choosing the right tools, and use the developer tools in turn constraints, ultimately achieve the purpose of control the overall project complexity, is to promote constantly the core power of a development team.

Yes, it’s the developers themselves that really make the difference, perfecting the basics and developing skills, starting with Redux 4.0.

Commercial Break: If you’re interested in front-end development, especially the React stack: My new book may have something you want to see. Follow author Lucas HC, there will be a book delivery event for the book release.

Happy Coding!

PS: author Github warehouse and Zhihu Q&A link welcome all forms of communication!

My other articles on the React technology stack:

Experience the design idea of React team from the discussion of setState promise

React App Design tips – Curry the way

Components reuse those things – React implements load wheels on demand

Learn best practices for writing the React component with examples

React component design and decomposition thinking

React binds this to javascript

Making Uber mobile web version is not enough to build the ultimate performance to see the real chapter **

React+Redux creates “NEWS EARLY”, a one-page app that understands the true nature of the cutting-edge technology stack