The introduction

Redux is a library used for global state management. It is often used with React. But why global state management?

Imagine that your project is now very large, with many components, each of which needs to communicate with each other. If we use the normal way, the communication between father and son would be very troublesome, because there are many components that are not the relationship between father and son, maybe they are the relationship between grandfather and grandson, maybe they are the relationship between brothers. In this case, if you need to pass state, you need to pass several layers, and subscribing to a publication with a message is complicated. This is where Redux is responsible for global state management.

Redux is a React independent library. React itself does not have a state-management plug-in like Vuex.

This article will start with the basics of Redux, walk you through the use of Redux in everyday projects, and use Redux to implement a simple commentary Demo.

Basic concept

What is a Redux?

Predictable State Container for JS Apps Redux is a PREDICTABLE state container for JS Apps.

The basic principle of

Before getting into the fundamentals of Redux, let’s clear up a few concepts:

concept describe
store At the heart of Redux, you can think of the COMPUTER’s CPU, which coordinates the hardware
view The components we write ourselves, the pages we see, will have various events occurring in the page that will produce a Dispatch similar to our input devices: keyboard, mouse, etc
action creator Create actions and leave them to the reducer. The actions they create are like instructions that tell the computer to work
reducer Where the operation is actually performed, the value of completion is returned

With these basic concepts in mind, let’s take a look at the basic execution flow of Redux

After we introduced Redux to our project, Redux will help us manage the state of the project. The STORE CPU will give various information to our components. When users need to use the information stored in Redux, they can use the store.state object to obtain the desired information. But the information is definitely not just for display, it’s definitely for manipulation, and the use of this component is to tell the store that I want to deal with the state in the store by calling the store.dispatch() method, which takes an action parameter, Action is a normal object with two properties {type: “, data: }, after the store receives the dispatch, the action and the previous state will be sent to reducer for modification. If the state is initialized, the reducer receives the action {type: ‘@@init [random string]’, data: }, after reducer processing, store will get the latest data and save it in state.

⚠️ Note: Redux only saves updates and does not actively render the page!

Redux of actual combat

Having talked briefly about the fundamentals and implementation of Redux, let’s start with a simple redux-based small project

Create a project

Use create-react-app to create a React scaffolding project

$ npx create-react-app redux-comment
Copy the code

Deal with the default files for your project

As you might expect, red files represent files that need to be deleted, green files are added, and gray files are modified

src/App.jsx

/* * @author: Mujey 🦦 * @date: 2021-08-16 14:20:20 */

import React, { Component } from 'react'

class App extends Component {
  render() {
    return (
      <div>
        <h1>Initial</h1>
      </div>)}}export default App
Copy the code

src/index.js

/* * @author: Mujey 🦦 * @date: 2021-08-16 13:48:30 */
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>.document.getElementById('root'))Copy the code

public/index.html

<! DOCTYPEhtml>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="# 000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <title>Comments With story</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
Copy the code

To make it easier to write code, I’m going to use a UI framework to build pages quickly. You can also use less or sass for styling, or use another UI framework. I’m using tailwind

Introduce tailwind CSS in your project

Install tailwindCSS

$ yarn add tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
Copy the code

Install and configure CRACO

$ yarn add @craco/craco
Copy the code

Change the project to start with CRACO

  "scripts": {
    "start": "craco start"."build": "craco build"."test": "craco test"."eject": "react-scripts eject"
  },
Copy the code

New craco. Config. Js

/* * @author: Mujey 🦦 * @date: 2021-08-17 13:51:34 */
module.exports = {
  style: {
    postcss: {
      plugins: [require('tailwindcss'), require('autoprefixer')],,}}},Copy the code

Generating a Configuration File

$ npx tailwindcss init
Copy the code

Modifying a Configuration File

/* * @author: Mujey 🦦 * @date: 2021-08-17 13:51:48 */
module.exports = {
  purge: ['./src/**/*.{js,jsx,ts,tsx}'.'./public/index.html'].darkMode: 'media'.// or 'media' or 'class'
  theme: {
    extend: {},},variants: {
    extend: {},},plugins: [],}Copy the code

Create a new file SRC /index.css

@tailwind base;
@tailwind components;
@tailwind utilities;
Copy the code

Introduced in index.js

import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
Copy the code

The official www.tailwindcss.cn/docs/guides…

Use the pure React implementation

Before using Redux, we tried using native React to communicate between components

Build pages quickly

Create three components

Modify the App. JSX

import React, { Component } from 'react'
import List from './components/List'
import Publish from './components/Publish'

class App extends Component {
  state = {
    comments: [],
  }
  addComment = obj= > {
    const { comments } = this.state
    this.setState({
      comments: [obj, ...comments],
    })
  }
  render() {
    const { comments } = this.state
    return (
      <div className="container-lg mx-auto w-8/12 h-screen py-16 antialiased">
        <Publish addComment={this.addComment} />
        <List comments={comments} />
      </div>)}}export default App
Copy the code

src/components/Publish.jsx

/* * @author: Mujey 🦦 * @date: 2021-08-17 15:10:27 */
import React, { Component } from 'react'
import dateformat from 'dateformat'
import { nanoid } from 'nanoid'

class Publish extends Component {
  handlePublish = e= > {
    if(e.keyCode ! = =13) return
    const { value } = this.inputNode
    const now = new Date(a)const obj = {
      value,
      time: dateformat(now, 'yyyy年MM月dd日 HH:MM:ss'),
      key: nanoid(),
    }
    // Call addComment under props to add a comment to the state of the App component
    this.props.addComment(obj)
    this.inputNode.value = ' '
  }
  render() {
    return (
      <div className="w-full h-28 p-4">
        <h1>Post a comment</h1>
        <div className="flex border-b-2 border-indigo-500 py-1 pl-3">
          <input
            ref={e= > (this.inputNode = e)}
            onKeyUp={this.handlePublish}
            type="text"
            className="flex-1 outline-none "
            autoFocus
          />
          <button
            onClick={this.handlePublish}
            className="bg-gradient-to-r from-indigo-300 to-indigo-400 px-9 py-1 text-white text-sm"
          >release</button>
        </div>
      </div>)}}export default Publish
Copy the code

src/components/List.jsx

/* * @author: Mujey 🦦 * @date: 2021-08-17 15:10:37 */
import React, { Component } from 'react'
import Item from './Item'

class List extends Component {
  render() {
    const { comments } = this.props
    return (
      <div>
        {comments.map(item => (
          <Item comment={item} key={item.key} />
        ))}
      </div>)}}export default List
Copy the code

Finally, the SRC/components/Item. JSX

/* * @author: Mujey 🦦 * @date: 2021-08-17 15:10:21 */
import React, { Component } from 'react'

class Item extends Component {
  render() {
    const {
      comment: { value, time },
    } = this.props
    return (
      <div className="flex items-center my-4 shadow-sm">
        <div>{value}</div>
        <div className="pl-4 text-sm text-gray-600 font-thin ml-auto">
          {time}
        </div>
      </div>)}}export default Item
Copy the code

In the code above, the Publish component receives a method called addComment from the App. By calling the method and passing the data, it stores the data to the state of the App component, which passes the data to the List component, which does a loop, And then the loop render Item group is going to render the data.

In the example above, the interaction is relatively simple, with the child calling the parent’s methods to pass parameters to the parent, and the parent passing parameters to the child. However, if the hierarchy is deep, it is not so easy to maintain. Let’s introduce Reudx for global state management.

Redux version

First, install the Redux dependency

$ yarn add redux
Copy the code

Configuration redux

Create a redux folder in the SRC directory to store everything about Redux, and create some new files in the Redux folder

├── ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─ ├─Copy the code

src/redux/store.js

/* * @author: Mujey 🦦 * @date: 2021-08-17 16:30:42 */
import { createStore } from 'redux'
import reducers from './reducers'

export default createStore(reducers)
Copy the code

A store created and exposed in store.js through the createStore function provided by Reudx

src/redux/reducers/comment.js

/* * @author: Mujey 🦦 * @date: 2021-08-17 16:31:30 */

const initialState = []
export default function commentReducer(prevState = initialState, action) {
  const { type, data } = action
  switch (type) {
    case 'addComment':
      return [data, ...prevState]
    default:
      return prevState
  }
}
Copy the code

This is the basic form of a reducer, he receives two parameters prevState coming to the store and the action, by judging the action. The type determine the return value of this method the SRC/story/reducers/index, js

/* * @author: Mujey 🦦 * @date: 2021-08-17 16:35:36 */
import { combineReducers } from 'redux'
import comment from './comment'

export default combineReducers({
  comment,
})
Copy the code

CombineReducers combines multiple reducers into one object, but now we only have a reducer. If you have multiple reducer, you just need to continue to add the reducer into the object of network parameters.

Finally, let’s write the action

src/redux/actions/comment.js

/* * @author: Mujey 🦦 * @date: 2021-08-17 16:31:22 */

export const addComment = data= > ({ type: 'addComment', data })
Copy the code

An action is a method used to create an action. We’ll use a synchronous aciton. We’ll talk about asynchronous actions later

Now that Reudx is ready for development, let’s modify our components and stop them communicating with each other.

Save all the states in store

Modify SRC/app.jsx to remove unnecessary code

import React, { Component } from 'react'
import List from './components/List'
import Publish from './components/Publish'

class App extends Component {
  render() {
    return (
      <div className="container-lg mx-auto w-8/12 h-screen py-16 antialiased">
        <Publish />
        <List />
      </div>)}}export default App
Copy the code

Modify SRC/components/Publis JSX, the introduction of the store and the action, use store. The dispatch () method to post a comment


import store from '.. /redux/store'
import { addComment } from '.. /redux/actions/comment'

class Publish extends Component {
  handlePublish = e= > {
    if(e.keyCode ! = =13) return
    const { value } = this.inputNode
    const now = new Date(a)const obj = {
      value,
      time: dateformat(now, 'yyyy年MM月dd日 HH:MM:ss'),
      key: nanoid(),
    }
    // Call store.dispatch to store comments
    store.dispatch(addComment(obj))
    this.inputNode.value = ' '
  }
  render() {
    / /...
Copy the code

Finally, modify SRC /components/ list.jsx to fetch data from store

import store from '.. /redux/store'

class List extends Component {
  render() {
    const comments = store.getState().comment
    / /...
Copy the code

At this point, the data is stored in Redux, but there should be no visible changes on the page for the simple reason that, as we mentioned earlier, Redux does not take care of the page rendering.

Fortunately, Redux provides the SUBSCRIBE API, which notifies us after each data update, and we manually render the page.

Modify the SRC/index. Js

import React from 'react'
import ReactDOM from 'react-dom'
import store from './redux/store'
import App from './App'
import './index.css'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>.document.getElementById('root')
)

store.subscribe(() = > {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>.document.getElementById('root'))})Copy the code

So far, the Redux version of the case is complete, and Redux helps us manage global state, which is great. But is there any room on top of that that can be optimized? Of course, because developers use Redux so much, React integrated a react version of Redux: React-Redux. We then used react-Redux to refactor our projects.

The react – redux version

First, an important concept. In React-Redux, it doesn’t allow our components to manipulate Redux directly. Instead, it provides easy components to manipulate Redux. In short, this is where React-Redux helps us connect to Redux. A little obscure? So let’s get right to it.

It’s the same first step: install dependencies

$ yarn add react-redux
Copy the code

Revamp the project directory structure

We need to create a folder, containers, and dump into it all the components that will operate on Redux in the future

├ ─ ─ components │ └ ─ ─ the Item. The JSX ├ ─ ─ containers │ ├ ─ ─ a List. The JSX │ └ ─ ─ the Publish the JSXCopy the code

Don’t forget to change the component reference path!

connect

By introducing the connect function from React-Redux, all container components are no longer exposed to the previous class, but to the connected container component

src/containers/Publis.jsx

import { connect } from 'react-redux'
// ...
class Publish extends Component {/ *... * /}

export default connect()(Publish)
Copy the code

So let’s explain the connect function, the function that connects the UI component to the container component, and now you have an idea of what a UI component is, what a container component is, and a UI component is the component that we’re laying out the page by hand; Container components are automatically generated using the CONNECT function that can operate on Redux.

The connect function takes two arguments. The first argument is a function to mount state to the PORps of the UI component, and the second argument, either a function or an object, to mount the Dispatch event to the props of the UI component

function mapStateToProps(state) {
    return {
       // ... }}function mapDispatchToProps(dispatch) {
    return {
        add() {
            dispatch(/* action */)}}}export default connect(mapStateToProps, mapDispatchToProps)(ComponentUI)
Copy the code

This is what a connect function should look like, but we can abbreviate it. In the CONNECT API, its second argument can be shortened to an object.

import { add } from '.. /redux/action/... '

export default connect(state= > (data: state.data), {add})(ComponentUI)
Copy the code

Ok! This is what connect looks like in everyday use, so let’s rewrite our code now

First we modify SRC /containers/ list.jsx to make it look like this

/* * @author: Mujey 🦦 * @date: 2021-08-17 15:10:37 */
import React, { Component } from 'react'
import { connect } from 'react-redux'

import Item from '.. /components/Item'

class List extends Component {
  render() {
    const { comments } = this.props
    return (
      <div>
        {comments.map(item => (
          <Item comment={item} key={item.key} />
        ))}
      </div>)}}export default connect(state= > ({ comments: state.comment }))(List)

Copy the code

Next is the SRC/containers/Publish. JSX

/* * @author: Mujey 🦦 * @date: 2021-08-17 15:10:27 */
import React, { Component } from 'react'
import dateformat from 'dateformat'
import { nanoid } from 'nanoid'
import { connect } from 'react-redux'

import { addComment } from '.. /redux/actions/comment'

class PublishUI extends Component {
  handlePublish = e= > {
    if(e.keyCode ! = =13) return
    const { value } = this.inputNode
    const now = new Date(a)const obj = {
      value,
      time: dateformat(now, 'yyyy年MM月dd日 HH:MM:ss'),
      key: nanoid(),
    }
    // new code ⏎ calls the addComment method in props
    this.props.addComment(obj)
    this.inputNode.value = ' '
  }
  render() {
    return (
      <div className="w-full h-28 p-4">
        <h1>Post a comment</h1>
        <div className="flex border-b-2 border-indigo-500 py-1 pl-3">
          <input
            ref={e= > (this.inputNode = e)}
            onKeyUp={this.handlePublish}
            type="text"
            className="flex-1 outline-none "
            autoFocus
          />
          <button
            onClick={this.handlePublish}
            className="bg-gradient-to-r from-indigo-300 to-indigo-400 px-9 py-1 text-white text-sm"
          >release</button>
        </div>
      </div>)}}export default connect(state= > ({ comments: state.comment }), { addComment })(
  PublishUI
)

Copy the code

If we start the project at this point, we will see the following error!

Don’t worry, this is because we haven’t injected store yet. Next we’ll modify SRC /index.js

/* * @author: Mujey 🦦 * @date: 2021-08-16 13:48:30 */
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
import App from './App'
import './index.css'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>.document.getElementById('root'))// store.subscribe(() => {
// ReactDOM.render(
// 
      
// 
      
// ,
// document.getElementById('root')
/ /)
// })
Copy the code

React-redux will render the DOM automatically. We just need to use the Provider component to provide stores wherever we need them

So far, the whole case has been completed.

And then finally, we’re going to do a little bit of refinement, which is the action step, when we create aciton, if we return an object, it’s a synchronous action, and if we return a function, it’s an asynchronous action

Asynchronous action

Install the story – thunk

$ yarn add redux-thunk
Copy the code

Modify SRC /redux/store.js to introduce redux-thunk middleware

/* * @author: Mujey 🦦 * @date: 2021-08-17 16:30:42 */
import { createStore, applyMiddleware } from 'redux'
import reducers from './reducers'
import thunk from 'redux-thunk'

export default createStore(reducers, applyMiddleware(thunk))
Copy the code

Add an addCommentAsync method to actions

export const addCommentAsync = data= > {
  return dispatch= > {
    setTimeout(() = > {
      dispatch({ type: 'addComment', data })
    }, 800)}}Copy the code

The use of setTimeout here creates asynchronous processing, simulating network requests. In the returned function, a dispath function can be accepted, which initiates a synchronous action to process the data.

Then change the synchronization method in pulish.jsx to a one-step method to see the results