How to use Redux in non react projects

Time:2021-9-26

catalogue

  • 1. Foreword

  • 2. Simply using Redux

    • 2.1. Question 1: code redundancy

    • 2.2. Problem 2: unnecessary rendering

  • 3. What did react Redux do

  • 4. Build “provider” and “connect” in your own project

    • 4.1 package rendering function

    • 4.2. Avoid unnecessary rendering

  • 5. Summary

  • 6. Practice

1. Foreword

Recently, I saw such a problem on Zhihu:Consult Redux and EventEmitter- Know

In a recent small project (not using react), I wanted to use Redux for management because there were a lot of changes in events and states, but I didn’t find it very convenient

Speaking of Redux, we usually say react. It seems that Redux and react are natural and should be bound together. In fact, the official positioning of Redux is:

Redux is a predictable state container for JavaScript apps.

Redux doesn’t mention react at all. It defines itself as “providing a predictable state container for JavaScript applications”. That is, you can use Redux in any JavaScript application that needs application state management.

However, once it is separated from the react environment, Redux seems to be out of control. It is rebellious and difficult to use. This article will take you to analyze the causes of the problem and provide an idea and scheme for using Redux in non react projects. This is not only helpful for using Redux in non react projects, but also helpful for understanding react redux.

This article assumes that readers have mastered the use of react, Redux, react Redux and the basic syntax of ES6.

2. Simply using Redux

Let’s use a very simple example to explain what problems will be encountered when using Redux in non react projects. Suppose there are three parts on the page, header, body and footer, which are rendered and controlled by different modules:

<div id='header'></div>
<div id='body'></div>
<div id='footer'></div>

This three part element may be shared and data may change. We store it in the store of Redux and simply build a store:

const appReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_HEADER':
      return Object.assign(state, { header: action.header })
    case 'UPDATE_BODY':
      return Object.assign(state, { body: action.body })
    case 'UPDATE_FOOTER':
      return Object.assign(state, { footer: action.footer })
    default:
      return state
  }
}

const store = Redux.createStore(appReducer, {
  header: 'Header',
  body: 'Body',
  footer: 'Footer'
})

Very simply, a reducer is defined above, which can be implemented through three different actions:UPDATE_HEADERUPDATE_BODYUPDATE_FOOTERTo modify the page data respectively.

With the store, the page is still blank because the data in the store is not taken out and rendered to the page. Next, build three rendering functions, where jQuery is used:

/*Render header*/
const renderHeader = () => {
  console.log('render header')
  $('#header').html(store.getState().header)
}
renderHeader()

/*Render body*/
const renderBody = () => {
  console.log('render body')
  $('#body').html(store.getState().body)
}
renderBody()

/*Render footer*/
const renderFooter = () => {
  console.log('render footer')
  $('#footer').html(store.getState().footer)
}
renderFooter()

Now you can see three on the pagedivThe contents of the element are:HeaderBodyFooter。 We intend to pass it in 1sstore.dispatchUpdate the data of the page to simulate the change of app data:

/*Data changes*/
setTimeout(() => {
  store.dispatch({ type: 'UPDATE_HEADER', header: 'New Header' })
  store.dispatch({ type: 'UPDATE_BODY', body: 'New Body' })
  store.dispatch({ type: 'UPDATE_FOOTER', footer: 'New Footer' })
}, 1000)

However, the page has not changed after 1s. Why? That is because the page is not re rendered when the data changes (the render method is called), so you need to pass thestore.subscribeSubscribe to events where data changes, and then re render different parts:

store.subscribe(renderHeder)
store.subscribe(renderBody)
store.subscribe(renderFooter)

Well, now jQuery and Redux are finally combined. Successfully managed the state that may change in this simple example with redux. But here are a few questions:

2.1. Question 1: code redundancy

After writing a rendering function, you need to manually initialize the first rendering; Then manually passstore.subscribeMonitor the data change of the store and call the rendering function again when the data changes. It’s all repetitive code and unnecessary work, and it may be forgottensubscribePossible.

2.2. Problem 2: unnecessary rendering

In the above example, the program performs an initialization rendering, and then the data update rendering. There is a log in the three rendering functions. The best case for two renderings should be only 6 logs.

However, you can see that 12 logs appear because of subsequent modificationsUPDATE_XXX, in addition to causing the data to be rendered, it will also cause the other two data to be re rendered (even if they have not actually changed).store.subscribeAll the listening functions are called, but there is no need to re render if the data does not change.

The above two shortcomings will become more and more prominent when the function is more complex.

3. What did react Redux do

It can be seen that simply using Redux and jQuery for visual inspection does not bring us any benefits and convenience. Is it OK to deny the use of Redux in non react projects?

Looking back, why didn’t the problems mentioned above occur when Redux and react were combined? You will find that react and Redux are not so violently combined as above. Between react and Redux, there is actually a third Library: react redux.

In the react + Redux project, we don’t need to do it manuallysubscribe, there is no need to manually optimize the performance. It is precisely because the dirty work is done by react Redux, and only one is provided externallyProviderandconnectThe method hides many details about the store operation.

Therefore, when combining Redux with ordinary projects, you can also refer to react Redux to build a tool library to hide details and simplify work.

That’s what needs to be done next. But before building this simple library, we need to know what react Redux does. What functions does react Redux provide us? In the react Redux project, we generally use:

import { connect, Provider } from 'react-redux'

/*Header component*/
class Header extends Component {
  render () {
    return (<div>{this.props.header}</div>)
  }
}

const mapStateToProps = (state) => {
  return { header: state.header }
}
Header = connect(mapStateToProps)(Header)

/*App components*/
class App extends Component {
  render () {
    return (
      <Provider store={store}>
        <Header />
      </Provider>
    )
  }
}

We putstorePassed on toProviderThen other components can be usedconnectPerform the operation of fetching data. It was passed in during connectmapStateToPropsmapStateToPropsIt plays a key role in extracting data. It can extract the data required by this component from the store on demand.

In fact, inside react Redux:ProviderAccept store as a parameter and pass the store to all sub components through context; Subcomponent passconnectWrapped with a layer of high-level components, which will be combined through contextmapStateToPropsandstoreThen the data is transmitted to the wrapped components.

If you don’t understand the above paragraph, you can refer to itImplement react Redux。 To put it bluntlyconnectThe function is actuallyProviderBased on, NoProviderthatconnectIt didn’t work.

The react component is responsible for rendering, which is equivalent to the render function in our example. Similar to react Redux components, we focus on rendering functions, which can provide different but similar functionsProviderandconnect

4. Build your own projectsProviderandconnect

4.1 package rendering function

Referring to react Redux, imagine a similar solution belowproviderandconnectIt can be applied to the jQuery example above:

/*Generate the connect function corresponding to this store through the provider*/
const connect = provider(store)

/*Common render method*/
let renderHeader = (props) => {
  console.log('render header')
  $('#header').html(props.header)
}

/*Use connect to get the data and pass it to the render method*/
const mapStateToProps = (state) => {
  return { header: state.header }
}
renderHeader = connect(mapStateToProps)(renderHeader)

As you can see, we just replaced the component with the render method. It’s the same as react redux. So how to buildproviderandconnectHow? Here’s a skeleton:

const provider = (store) => {
  Return (mapstatetoprops) = > {// connect function
    return (render) => {
      /* TODO */
    }
  }
}

provideracceptstoreAs a parameter, returns aconnectFunction;connectFunction acceptancemapStateToPropsReturn a new function as a parameter; The returned function is similar to react Redux, which accepts a component (rendering function) as a parameter, and its content is the code to be implemented next. Of course, it can also be represented by multiple arrows:

const provider = (store) => (mapStateToProps) => (render) => {
  /* TODO */
}

storemapStateToPropsrenderThere are all of them. The rest is to take out the data in the store and pass it to the usermapStateToPropsTo getprops; Then putpropsPass torenderFunction.

const provider = (store) => (mapStateToProps) => (render) => {
  /*Return a new rendering function, just like connect of react Redux returns a new component*/
  const renderWrapper = () => {
    const props = mapStateToProps(store.getState())
    render(props)
  }
  return renderWrapper
}

At this time, the hypothetical code at the beginning of this section can be rendered normally. Rewrite the code of other parts in the same way:

/* body */
let renderBody = (props) => {
  console.log('render body')
  $('#body').html(props.body)
}
mapStateToProps = (state) => {
  return { body: state.body }
}
renderBody = connect(mapStateToProps)(renderBody)

/* footer */
let renderFooter = (props) => {
  console.log('render footer')
  $('#footer').html(props.footer)
}
mapStateToProps = (state) => {
  return { footer: state.footer }
}
renderFooter = connect(mapStateToProps)(renderFooter)

Although the page can be rendered. But at this timestore.dispatchIt will not cause re rendering. We can subscribe in connect:

const provider = (store) => (mapStateToProps) => (render) => {
  /*Return a new rendering function, just as react Redux returns a new component*/
  const renderWrapper = () => {
    const props = mapStateToProps(store.getState())
    render(props)
  }
  /*Listen for data changes and re render*/
  store.subscribe(renderWrapper)
  return renderWrapper
}

fabulous. Now?store.dispatchIt can cause the page to be re rendered, which is the same as the original function. However, the console still prints 12 logs, which still does not solve the problem of re rendering caused by irrelevant data changes.

4.2. Avoid unnecessary rendering

In the above code, each timestore.dispatchWill lead torenderWrapperFunction, it willstore.getState()Pass tomapStateToPropsTo calculate the newpropsThen pass it on torender

In fact, you can do something here: cache the props of the last calculation, and then compare the new props with the old props. If they are the same, they will not be calledrender

const provider = (store) => (mapStateToProps) => (render) => {
  /*Cache props*/
  let props
  const renderWrapper = () => {
    const newProps = mapStateToProps(store.getState())
    /*If the new result is the same as the original, don't re render it*/
    if (shallowEqual(props, newProps)) return
    props = newProps
    render(props)
  }
  /*Listen for data changes and re render*/
  store.subscribe(renderWrapper)
  return renderWrapper
}

The key point here isshallowEqual。 becausemapStateToPropsDifferent objects will be returned every time, so they cannot be used directly===To determine whether the data has changed. Here we can judge the of two objectsLayer 1 dataWhether they are all the same. If they are the same, there is no need to re render. For example:

const a = { name: 'jerry' }
const b = { name: 'jerry' }

a === b // false
shallowEqual(a, b) // true

At this time, look at the console. There are only six logs. The purpose of performance optimization is successfully achieved. hereshallowEqualThe implementation of is left to the reader to practice.

Here, a binding similar to react Redux has been completed, which can be used happily in non react projects. The complete code can see thisgist

5. Summary

It can be seen from this article that in non react projects combined with Redux, the two can not be used simply and rudely. To build the tool library required in this scenario according to the needs of the project to simplify the operation of the store, of course, you can directly refer to the implementation of react Redux for corresponding binding.

It can also be concluded that the connect of react Redux helps us hide a lot of store operations, including monitoring and re rendering of store data changes, data comparison and performance optimization.

6. Practice

If you are interested in the content of this article, you can do the supporting exercises of this article:

  1. Implement a shallowequal

  2. Add mapdispatchtoprops to the provider

Recommended Today

Seven Python code review tools recommended

althoughPythonLanguage is one of the most flexible development languages at present, but developers often abuse its flexibility and even violate relevant standards. So PythoncodeThe following common quality problems often occur: Some unused modules have been imported Function is missing arguments in various calls The appropriate format indentation is missing Missing appropriate spaces before and after […]