web-dev-qa-db-fra.com

Comment bien faire REST appels de l'application ReactJS + Redux?

J'utilise ReactJS + Redux, ainsi que Express et Webpack. Une API est construite et je souhaite pouvoir effectuer des appels REST - GET, POST, PUT, DELETE - à partir du côté client. 

Comment et comment s'y prendre avec l'architecture Redux? Tout bon exemple de flux, en termes de réducteurs, de créateurs d’action, de magasins et de routes réactives, serait extrêmement utile. 

Merci d'avance!

18
Jo Ko

La manière la plus simple est de le faire en utilisant le package redux-thunk. Ce paquet est un middleware redux, vous devez donc tout d'abord le connecter à redux:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

Cela vous permet d’envoyer des actions async avec des actions sync régulières. Créons l'un d'entre eux:

// actions.js

export function fetchTodos() {
  // Instead of plain objects, we are returning function.
  return function(dispatch) {
    // Dispatching REQUEST action, which tells our app, that we are started requesting todos.
    dispatch({
      type: 'FETCH_TODOS_REQUEST'
    });
    return fetch('/api/todos')
      // Here, we are getting json body(in our case it will contain `todos` or `error` prop, depending on request was failed or not) from server response
      // And providing `response` and `body` variables to the next chain.
      .then(response => response.json().then(body => ({ response, body })))
      .then(({ response, body }) => {
        if (!response.ok) {
          // If request was failed, dispatching FAILURE action.
          dispatch({
            type: 'FETCH_TODOS_FAILURE',
            error: body.error
          });
        } else {
          // When everything is ok, dispatching SUCCESS action.
          dispatch({
            type: 'FETCH_TODOS_SUCCESS',
            todos: body.todos
          });
        }
      });
  }
}

Je préfère séparer les composants de réaction des composants de présentation et des composants de conteneur. Cette approche a été parfaitement décrite dans cet article .

Ensuite, nous devrions créer le composant TodosContainer, qui fournirait des données au composant presentationnel Todos. Ici, nous utilisons la bibliothèque react-redux:

// TodosContainer.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchTodos } from '../actions';

class TodosContainer extends Component {
  componentDidMount() {
    // When container was mounted, we need to start fetching todos.
    this.props.fetchTodos();
  }

  render() {
    // In some simple cases, it is not necessary to create separate `Todos` component. You can put todos markup directly here.
    return <Todos items={this.props.todos} />
  }
}

// This function is used to convert redux global state to desired props.
function mapStateToProps(state) {
  // `state` variable contains whole redux state.
  return {
    // I assume, you have `todos` state variable.
    // Todos will be available in container component as `this.props.todos`
    todos: state.todos
  };
}

// This function is used to provide callbacks to container component.
function mapDispatchToProps(dispatch) {
  return {
    // This function will be available in component as `this.props.fetchTodos`
    fetchTodos: function() {
      dispatch(fetchTodos());
    }
  };
}

// We are using `connect` function to wrap our component with special component, which will provide to container all needed data.
export default connect(mapStateToProps, mapDispatchToProps)(TodosContainer);

En outre, vous devez créer todosReducer, qui gérera l'action FETCH_TODOS_SUCCESS, ainsi que deux autres actions si vous souhaitez afficher le message chargeur/erreur.

// reducers.js

import { combineReducers } from 'redux';

const INITIAL_STATE = {
  items: [],
  isFetching: false,
  error: undefined
};

function todosReducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case 'FETCH_TODOS_REQUEST':
      // This time, you may want to display loader in the UI.
      return Object.assign({}, state, {
        isFetching: true
      });
    case 'FETCH_TODOS_SUCCESS':
      // Adding derived todos to state
      return Object.assign({}, state, {
        isFetching: false,
        todos: action.todos
      });
    case 'FETCH_TODOS_FAILURE':
      // Providing error message to state, to be able display it in UI.
      return Object.assign({}, state, {
        isFetching: false,
        error: action.error
      });
    default:
      return state;
  }
}

export default combineReducers({
  todos: todosReducer
});

Pour d'autres opérations comme CREATE, UPDATE, DELETE, il n'y a rien de spécial, elles implémentent de la même manière.

21
1ven

La réponse courte est:

  1. redux n'est pas une architecture
  2. Vous pouvez utiliser n'importe quelle bibliothèque. De nos jours, beaucoup de gens utilisent directement l'API d'extraction.
  3. Pour pouvoir intégrer redux avec des actions asynchrones (dont vous avez besoin pour AJAX), vous devez utiliser une bibliothèque pour vous aider. Les deux plus populaires sont redux-thunk et redux-saga, comme d’autres l’ont dit.

Pour une bibliothèque simple et mortelle que vous pouvez déposer dans votre application redux, vous pouvez essayer redux-crud-store . Avertissement: je l'ai écrit. Vous pouvez également lire le code source de redux-crud-store si vous souhaitez intégrer l'API d'extraction ou un autre client d'API avec redux-saga.

2
Devin Howard

Ceci est le principal cas d'utilisation de bibliothèques telles que redux-thunk , redux-saga et redux-observable .

redux-thunk est le plus simple, où vous feriez quelque chose comme ceci:

import fetch from 'isomorphic-fetch'

export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
  return {
    type: REQUEST_POSTS,
    subreddit
  }
}

export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
  return {
    type: RECEIVE_POSTS,
    subreddit,
    posts: json.data.children.map(child => child.data),
    receivedAt: Date.now()
  }
}

// Meet our first thunk action creator!
// Though its insides are different, you would use it just like any other action creator:
// store.dispatch(fetchPosts('reactjs'))

export function fetchPosts(subreddit) {

  // Thunk middleware knows how to handle functions.
  // It passes the dispatch method as an argument to the function,
  // thus making it able to dispatch actions itself.

  return function (dispatch) {

    // First dispatch: the app state is updated to inform
    // that the API call is starting.

    dispatch(requestPosts(subreddit))

    // The function called by the thunk middleware can return a value,
    // that is passed on as the return value of the dispatch method.

    // In this case, we return a promise to wait for.
    // This is not required by thunk middleware, but it is convenient for us.

    return fetch(`http://www.reddit.com/r/${subreddit}.json`)
      .then(response => response.json())
      .then(json =>

        // We can dispatch many times!
        // Here, we update the app state with the results of the API call.

        dispatch(receivePosts(subreddit, json))
      )

      // In a real world app, you also want to
      // catch any error in the network call.
  }
}

L'exemple ci-dessus est tiré directement de http://redux.js.org/docs/advanced/AsyncActions.html , qui est en réalité la source définitive de réponses à votre question.

1
Nathan Hagen