Add Redux in React project

2020-06-29 08:21发布

问题:

Adding redux in React project (Refactor a simple project with Redux)

Consider a simple project, a counter application that works with two buttons, once for Increment and another for Decrement counter value.

In an actual scenario, we use the state for holding counter value like this:

in App.js:

import React, {Component} from 'react';
import CounterApp from './CounterApp'

class App extends Component {
  render() {
    return (
      <CounterApp/>
    );
  }
}

export default App;

in CounterApp.js:

import React, {Component} from 'react';

class CounterApp extends Component {
  state = {
    counter: 0
  };

  handleIncrement = () => {
    this.setState(prevState => ({
      counter: prevState.counter + 1
    }))
  };

  handleDecrement = () => {
    this.setState(prevState => ({
      counter: prevState.counter - 1
    }))
  };

  render() {
    return (
      <div>
        <button onClick={this.handleIncrement}>Increment</button>
        <p>{this.state.counter}</p>
        <button onClick={this.handleDecrement}>Decrement</button>
      </div>
    );
  }
}

export default CounterApp;

A simple and basic example that implement with react class component and handled by two function handler (handleIncrement and handleDecrement)

And a state with a value, counter

I'm using prevState because of it's a best practice when you forced to use this.state. in setState!

Now, what would be this implementation with Redux?

回答1:

First of all, you need to install redux and react-redux packages to your project via npm or yarn.

You can simply install them with one line of code:

npm install redux react-redux --save

or with yarn:

yarn add redux react-redux

now back to project and create 3 files with these names:

action.js, reducer.js and store.js

open action.js file. We should implement two actions for this app. One for increment and one for decrement.

in action.js

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

const increment = () => ({type: INCREMENT_COUNTER});
const decrement = () => ({type: DECREMENT_COUNTER});

export {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  increment,
  decrement
}

actions are simple functions that dispatched from component to redux for changing the store(state) via reducers.

so we should change reducer.js:

import {INCREMENT_COUNTER, DECREMENT_COUNTER} from "./action";

const initialState = {
  counter: 0
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case(INCREMENT_COUNTER):
      return {
        ...state,
        counter: state.counter + 1
      };
    case (DECREMENT_COUNTER):
      return {
        ...state,
        counter: state.counter - 1
      };
    default:
      return state
  }
};

export default reducer

There are 3 main principles of using redux:

1- Single source of truth. The state of your whole application is stored in an object tree within a single store.

2- The state is read-only. The only way to change the state is to emit an action, an object describing what happened.

3- Changes are made with pure functions.

according to second principles, we must assume that the state is immutable, and each case(in switch) must return state individually. using ...state in the returned state means that if initialState will changing in future, these cases will work properly (in this example it's not necessary).

our functions in actions are pure(3rd principle)

and for last new file store.js:

import {createStore} from "redux";
import reducer from './reducer'

const store = createStore(reducer);

export default store;

now we should apply this store to our App component. so open App.js file and made these changes:

in App.js:

import React, {Component} from 'react';
import CounterApp from './CounterApp'
import {Provider} from 'react-redux'
import store from './store'

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <CounterApp/>
      </Provider>
    );
  }
}

export default App;

Provider wrapped the CounterApp component and will propagate store to App and CounterApp and all other child components.

finally, change the CounterApp.js:

import React, {Component} from 'react';
import {connect} from "react-redux";
import {increment, decrement} from "./action";

class CounterApp extends Component {

  handleIncrement = () => this.props.dispatch(increment());

  handleDecrement = () => this.props.dispatch(decrement());

  render() {
    return (
      <div>
        <button onClick={this.handleIncrement}>Increment</button>
        <p>{this.props.counter}</p>
        <button onClick={this.handleDecrement}>Decrement</button>
      </div>
    );
  }
}

const mapStateToProps = state => {
  const counter = state.counter;
  return {counter}
};

export default connect(mapStateToProps)(CounterApp);

we are using increment & decrement actions to dispatch actions to redux.

the state was removed and instead of state we create a special function mapStateToProps' and useconnect` to connect the state to component props.

That's done!