React-redux divides all components into two broad categories: UI components (Presentational Components) and container Components (Container Components).

I. UI components

UI components have the following characteristics.

  • Only responsible for the presentation of the UI, with no business logic
  • No state (that is, not usedthis.stateThis variable)
  • All data is defined by parameters (this.props) to provide
  • Do not use any of Redux’s apis

Here is an example of a UI component.

const Title =
  value => <h1>{value}</h1>;
Copy the code

Because it has no state, a UI component is called a “pure component,” meaning that it is purely a function whose values are determined by parameters.

Container components

Container components have the opposite characteristics.

  • Responsible for managing data and business logic, not UI rendering
  • With internal state
  • Use Redux’s API

In summary, just remember one sentence: THE UI component is responsible for rendering the UI, and the container component is responsible for managing the data and logic.

What if a component has both UI and business logic, you might ask? The answer is to break it down into the following structure: a container component on the outside and a UI component on the inside. The former is responsible for communicating with the outside world, passing the data to the latter, who renders the view.

React-redux specifies that all UI components are provided by the user, while container components are automatically generated by React-Redux. In other words, the user takes care of the visual layer, leaving state management to it.

Three, the connect ()

React-redux provides the connect method for generating container components from UI components. Connect means to connect the two components together.

import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
Copy the code

In the code above, TodoList is the UI component, and VisibleTodoList is the container component automatically generated by React-Redux via the connect method.

However, because the business logic is not defined, the container component above is meaningless and is simply a wrapper layer for the UI component. To define the business logic, you need to give two pieces of information.

(1) Input logic: how can external data (i.e. state objects) be converted into parameters of UI components

(2) Output logic: how the actions issued by the user become Action objects, out of the UI component.

Thus, the complete API for the CONNECT method is as follows.

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
Copy the code

In the code above, the connect method takes two parameters: mapStateToProps and mapDispatchToProps. They define the business logic of the UI components. The former is responsible for the input logic, which maps state to the UI component’s parameters (props), and the latter is responsible for the output logic, which maps user actions on the UI component to actions

Four, mapStateToProps ()

MapStateToProps is a function. Its job, as its name suggests, is to establish a mapping between the (external) state object and the (UI component’s) props object.

As a function, mapStateToProps should return an object in which each key-value pair is a mapping. Take a look at the following example.

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
Copy the code

In the code above, mapStateToProps is a function that takes state as an argument and returns an object. This object has a todos attribute that represents the UI component’s nameless parameter, followed by getVisibleTodos, which is also a function that calculates the toDOS value from state.

Here is an example of getVisibleTodos to work out toDOS.

const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => ! t.completed) default: throw new Error('Unknown filter: ' + filter) } }Copy the code

MapStateToProps subscribes to the Store, and every time state is updated, it automatically recalculates the PARAMETERS of the UI component, triggering a re-rendering of the UI component.

The first argument to mapStateToProps is always the state object. You can also use the second argument, which represents the props object of the container component.

<FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }Copy the code

Using ownProps as a parameter also causes the UI component to re-render if the parameters of the container component change.

The connect method can omit the mapStateToProps parameter so that the UI component does not subscribe to the Store, meaning that Store updates do not cause UI component updates.

Fifth, mapDispatchToProps ()

MapDispatchToProps is the second parameter to the connect function that maps UI component parameters to the Store. dispatch method. That is, it defines which user actions should be passed to the Store as actions. It can be a function or an object.

If mapDispatchToProps were a function, it would take two arguments, Dispatch and ownProps (the props object of the container component).

const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); }}; }Copy the code

As you can see from the code above, mapDispatchToProps, as a function, should return an object where each key-value pair is a mapping that defines how the UI component’s parameters emit actions.

If mapDispatchToProps were an object, and each of its key names was the same parameter as the corresponding UI component, the key value would be a function that would be treated as an Action Creator, and the returned Action would be automatically emitted by Redux. For example, the mapDispatchToProps above is written as an object like this.

const mapDispatchToProps = {
  onClick: (filter) => {
    type: 'SET_VISIBILITY_FILTER',
    filter: filter
  };
}
Copy the code

Six, components,

After the connect method generates the container component, it needs to get the state object from the container component to generate the UI component’s parameters.

One solution is to pass a state object as a parameter to the container component. However, this can be cumbersome, especially if container components are at very deep levels, and passing state down from one level to the next is cumbersome.

React-redux provides a Provider component that lets container components get state.

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

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

In the code above, the Provider wraps a layer around the root component so that all child components of the App get state by default.

It works by using the React component context property.

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}
Copy the code

In the above code, store is placed above the context object context. Then, the child component can get from the context to the store, and the code looks like this.

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}
Copy the code

React-redux automatically generates the code for the container component, like the one above, to get to the store.

7. Example: counters

Let’s look at an example. Below is a counter component that is a pure UI component.

class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}
Copy the code

In the code above, the UI component has two parameters: value and onIncreaseClick. The former needs to be computed from state, and the latter needs to issue the Action.

Next, define the value to state mapping and the onIncreaseClick to Dispatch mapping.

function mapStateToProps(state) {
  return {
    value: state.count
  }
}

function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// Action Creator
const increaseAction = { type: 'increase' }
Copy the code

The container component is then generated using the CONNECT method.

const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)
Copy the code

Then, define the Reducer of this component.

// Reducer
function counter(state = { count: 0 }, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return { count: count + 1 }
    default:
      return state
  }
}
Copy the code

Finally, a Store object is generated and a Provider is used to cover the root component.

import { loadState, saveState } from './localStorage';

const persistedState = loadState();
const store = createStore(
  todoApp,
  persistedState
);

store.subscribe(throttle(() => {
  saveState({
    todos: store.getState().todos,
  })
}, 1000))

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

See the full code here.

React-router Router library

A project using react-Router is no different from other projects in that it also uses a Provider to cover the Router. After all, the only function of a Provider is to pass in store objects.

const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);
Copy the code