javascript - servicio - react consume rest api
¿Cómo realizar correctamente las llamadas REST desde la aplicación ReactJS+Redux? (3)
Estoy usando ReactJS + Redux, junto con Express y Webpack. Se construyó una API y quiero poder realizar llamadas REST - GET, POST, PUT, DELETE - desde el lado del cliente.
¿Cómo y cuál es la forma correcta de hacerlo con la arquitectura Redux? Cualquier buen ejemplo del flujo, en términos de reductores, creadores de acción, almacenamiento y rutas de reacción, sería extremadamente útil.
¡Gracias de antemano!
Este es el caso de uso principal para bibliotecas como redux-thunk
, redux-saga
y redux-observable
.
redux-thunk
es el más simple, donde harías algo como esto:
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.
}
}
El ejemplo anterior se toma directamente de http://redux.js.org/docs/advanced/AsyncActions.html que es realmente la fuente definitiva de respuestas a su pregunta.
La respuesta corta es:
- redux no es una arquitectura
- Puedes usar cualquier biblioteca. Mucha gente en estos días usa la API fetch directamente.
- Para poder integrar redux con acciones asíncronas (que necesita para AJAX), necesita usar una biblioteca para ayudar. Los dos más populares son
redux-thunk
yredux-saga
, como han dicho otros.
Para una biblioteca simple con muerte cerebral que puede colocar en su aplicación de redux, puede probar redux-crud-store . Descargo de responsabilidad: lo escribí. También puede leer la fuente de redux-crud-store si está interesado en integrar la API de búsqueda, u otro cliente de API, con redux-saga.
La forma más sencilla es hacerlo usando el paquete redux-thunk
. Este paquete es un middleware de redux, por lo que primero debe conectarlo a redux:
import { createStore, applyMiddleware } from ''redux'';
import thunk from ''redux-thunk'';
import rootReducer from ''./reducers/index'';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Esto le permite enviar acciones async
junto con acciones de sync
regulares. Vamos a crear uno de ellos:
// 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
});
}
});
}
}
Prefiero separar los componentes de reacción en los componentes de presentación y contenedor. Este enfoque fue perfectamente descrito en este artículo .
A continuación, deberíamos crear el componente TodosContainer
, que proporcionaría datos al componente Todos
presentación. Aquí, estamos usando la librería 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);
Además, debe crear todosReducer
, que manejará la acción FETCH_TODOS_SUCCESS
, y otras 2 acciones si desea mostrar el cargador / mensaje de error.
// 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
});
Para otras operaciones como CREATE
, UPDATE
, DELETE
, no hay nada especial, se están implementando de la misma manera.