sirve react que para español javascript asynchronous reactjs redux redux-thunk

javascript - react - redux saga español



¿Por qué necesitamos middleware para el flujo asíncrono en Redux? (9)

Según los documentos, "Sin middleware, la tienda Redux solo admite el flujo de datos síncrono" . No entiendo por qué este es el caso. ¿Por qué el componente contenedor no puede llamar a la API asíncrona y luego dispatch las acciones?

Por ejemplo, imagine una interfaz de usuario simple: un campo y un botón. Cuando el usuario presiona el botón, el campo se llena con datos de un servidor remoto.

import * as React from ''react''; import * as Redux from ''redux''; import { Provider, connect } from ''react-redux''; const ActionTypes = { STARTED_UPDATING: ''STARTED_UPDATING'', UPDATED: ''UPDATED'' }; class AsyncApi { static getFieldValue() { const promise = new Promise((resolve) => { setTimeout(() => { resolve(Math.floor(Math.random() * 100)); }, 1000); }); return promise; } } class App extends React.Component { render() { return ( <div> <input value={this.props.field}/> <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button> {this.props.isWaiting && <div>Waiting...</div>} </div> ); } } App.propTypes = { dispatch: React.PropTypes.func, field: React.PropTypes.any, isWaiting: React.PropTypes.bool }; const reducer = (state = { field: ''No data'', isWaiting: false }, action) => { switch (action.type) { case ActionTypes.STARTED_UPDATING: return { ...state, isWaiting: true }; case ActionTypes.UPDATED: return { ...state, isWaiting: false, field: action.payload }; default: return state; } }; const store = Redux.createStore(reducer); const ConnectedApp = connect( (state) => { return { ...state }; }, (dispatch) => { return { update: () => { dispatch({ type: ActionTypes.STARTED_UPDATING }); AsyncApi.getFieldValue() .then(result => dispatch({ type: ActionTypes.UPDATED, payload: result })); } }; })(App); export default class extends React.Component { render() { return <Provider store={store}><ConnectedApp/></Provider>; } }

Cuando se procesa el componente exportado, puedo hacer clic en el botón y la entrada se actualiza correctamente.

Tenga en cuenta la función de update en la llamada de connect . Envía una acción que le dice a la aplicación que se está actualizando y luego realiza una llamada asíncrona. Una vez que finaliza la llamada, el valor proporcionado se distribuye como una carga útil de otra acción.

¿Qué hay de malo en este enfoque? ¿Por qué querría usar Redux Thunk o Redux Promise, como sugiere la documentación?

EDITAR: Busqué pistas en el repositorio de Redux y descubrí que los creadores de acción debían ser funciones puras en el pasado. Por ejemplo, aquí hay un usuario que intenta proporcionar una mejor explicación para el flujo de datos asíncrono:

El creador de acción en sí mismo sigue siendo una función pura, pero la función thunk que devuelve no necesita serlo, y puede hacer nuestras llamadas asíncronas

Los creadores de acciones ya no están obligados a ser puros. Entonces, el middleware thunk / promise definitivamente se requería en el pasado, pero parece que este ya no es el caso.


¿Qué hay de malo en este enfoque? ¿Por qué querría usar Redux Thunk o Redux Promise, como sugiere la documentación?

No hay nada malo con este enfoque. Es simplemente inconveniente en una aplicación grande porque tendrás diferentes componentes realizando las mismas acciones, es posible que desees eliminar algunas acciones o mantener algún estado local como ID de incremento automático cerca de los creadores de acciones, etc. Por lo tanto, es más fácil El punto de vista de mantenimiento para extraer creadores de acciones en funciones separadas.

Puede leer mi respuesta a "Cómo enviar una acción de Redux con un tiempo de espera" para obtener un tutorial más detallado.

El middleware como Redux Thunk o Redux Promise solo le da "azúcar de sintaxis" para enviar thunks o promesas, pero no tiene que usarlo.

Entonces, sin ningún middleware, su creador de acción podría verse así

// action creator function loadData(dispatch, userId) { // needs to dispatch, so it is first argument return fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_DATA_FAILURE'', err }) ); } // component componentWillMount() { loadData(this.props.dispatch, this.props.userId); // don''t forget to pass dispatch }

Pero con Thunk Middleware puedes escribirlo así:

// action creator function loadData(userId) { return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_DATA_FAILURE'', err }) ); } // component componentWillMount() { this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do }

Entonces no hay una gran diferencia. Una cosa que me gusta del último enfoque es que al componente no le importa que el creador de la acción sea asíncrono. Simplemente llama dispatch normalmente, también puede usar mapDispatchToProps para vincular a tal creador de acción con una sintaxis corta, etc. Los componentes no saben cómo se implementan los creadores de acción, y puede cambiar entre diferentes enfoques asincrónicos (Redux Thunk, Redux Promise, Redux Saga) sin cambiar los componentes. Por otro lado, con el primer enfoque explícito, sus componentes saben exactamente que una llamada específica es asíncrona y necesita un dispatch para ser aprobada por alguna convención (por ejemplo, como un parámetro de sincronización).

También piense en cómo cambiará este código. Supongamos que queremos tener una segunda función de carga de datos y combinarlos en un solo creador de acciones.

Con el primer enfoque, debemos tener en cuenta a qué tipo de creador de acción estamos llamando:

// action creators function loadSomeData(dispatch, userId) { return fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_SOME_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_SOME_DATA_FAILURE'', err }) ); } function loadOtherData(dispatch, userId) { return fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_OTHER_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_OTHER_DATA_FAILURE'', err }) ); } function loadAllData(dispatch, userId) { return Promise.all( loadSomeData(dispatch, userId), // pass dispatch first: it''s async loadOtherData(dispatch, userId) // pass dispatch first: it''s async ); } // component componentWillMount() { loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first }

Con Redux Thunk, los creadores de acciones pueden dispatch el resultado de otros creadores de acciones y ni siquiera pensar si son síncronos o asíncronos:

// action creators function loadSomeData(userId) { return dispatch => fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_SOME_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_SOME_DATA_FAILURE'', err }) ); } function loadOtherData(userId) { return dispatch => fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_OTHER_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_OTHER_DATA_FAILURE'', err }) ); } function loadAllData(userId) { return dispatch => Promise.all( dispatch(loadSomeData(userId)), // just dispatch normally! dispatch(loadOtherData(userId)) // just dispatch normally! ); } // component componentWillMount() { this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally! }

Con este enfoque, si luego desea que sus creadores de acciones analicen el estado actual de Redux, puede usar el segundo argumento getState pasado a los thunks sin modificar el código de llamada en absoluto:

function loadSomeData(userId) { // Thanks to Redux Thunk I can use getState() here without changing callers return (dispatch, getState) => { if (getState().data[userId].isLoaded) { return Promise.resolve(); } fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''LOAD_SOME_DATA_SUCCESS'', data }), err => dispatch({ type: ''LOAD_SOME_DATA_FAILURE'', err }) ); } }

Si necesita cambiarlo para que sea sincrónico, también puede hacerlo sin cambiar ningún código de llamada:

// I can change it to be a regular action creator without touching callers function loadSomeData(userId) { return { type: ''LOAD_SOME_DATA_SUCCESS'', data: localStorage.getItem(''my-data'') } }

Entonces, el beneficio de usar middleware como Redux Thunk o Redux Promise es que los componentes no son conscientes de cómo se implementan los creadores de acciones, y si les importa el estado de Redux, si son síncronos o asíncronos, y si llaman o no a otros creadores de acciones . La desventaja es un poco indirecta, pero creemos que vale la pena en aplicaciones reales.

Finalmente, Redux Thunk y sus amigos son solo un posible enfoque para las solicitudes asincrónicas en las aplicaciones de Redux. Otro enfoque interesante es Redux Saga, que le permite definir demonios de larga duración ("sagas") que toman medidas a medida que se presentan y transforman o realizan solicitudes antes de generar acciones. Esto mueve la lógica de los creadores de acción a las sagas. Es posible que desee verlo y luego elegir lo que más le convenga.

Busqué pistas en el repositorio de Redux y descubrí que los creadores de acción debían ser funciones puras en el pasado.

Esto es incorrecto. Los documentos dijeron esto, pero los documentos estaban equivocados.
Los creadores de acciones nunca tuvieron que ser funciones puras.
Arreglamos los documentos para reflejar eso.


Usted no

Pero ... deberías usar redux-saga :)

La respuesta de Dan Abramov es correcta sobre redux-thunk pero hablaré un poco más sobre redux-saga, que es bastante similar pero más poderosa.

Imperativo VS declarativo

  • DOM : jQuery es imperativo / React es declarativo
  • Mónadas : IO es imperativo / Free es declarativo
  • Efectos de Redux : redux-thunk es imperativo / redux-saga es declarativo

Cuando tienes un golpe en tus manos, como una mónada IO o una promesa, no puedes saber fácilmente qué hará una vez que ejecutes. La única forma de probar un thunk es ejecutarlo y burlarse del despachador (o de todo el mundo exterior si interactúa con más cosas ...).

Si está utilizando simulacros, entonces no está haciendo una programación funcional.

Visto a través de la lente de los efectos secundarios, los simulacros son una señal de que su código es impuro y, a los ojos del programador funcional, una prueba de que algo está mal. En lugar de descargar una biblioteca para ayudarnos a verificar que el iceberg esté intacto, deberíamos navegar por él. Un tipo duro de TDD / Java una vez me preguntó cómo te burlas en Clojure. La respuesta es, generalmente no lo hacemos. Generalmente lo vemos como una señal de que necesitamos refactorizar nuestro código.

Source

Las sagas (como se implementaron en redux-saga ) son declarativas y, al igual que la mónada libre o los componentes Reaccionar, son mucho más fáciles de probar sin ninguna simulación.

Ver también este article :

en la FP moderna, no deberíamos escribir programas, deberíamos escribir descripciones de los programas, que luego podemos introspectar, transformar e interpretar a voluntad.

(En realidad, Redux-saga es como un híbrido: el flujo es imperativo pero los efectos son declarativos)

Confusión: acciones / eventos / comandos ...

Hay mucha confusión en el mundo frontend sobre cómo algunos conceptos de backend como CQRS / EventSourcing y Flux / Redux pueden estar relacionados, principalmente porque en Flux usamos el término "acción" que a veces puede representar tanto código imperativo ( LOAD_USER ) como eventos ( USER_LOADED ). Creo que, al igual que el abastecimiento de eventos, solo debe enviar eventos.

Usando sagas en la práctica

Imagine una aplicación con un enlace a un perfil de usuario. La forma idiomática de manejar esto con ambos middlewares sería:

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div> function loadUserProfile(userId) { return dispatch => fetch(`http://data.com/${userId}`) .then(res => res.json()) .then( data => dispatch({ type: ''USER_PROFILE_LOADED'', data }), err => dispatch({ type: ''USER_PROFILE_LOAD_FAILED'', err }) ); }

redux-saga

<div onClick={e => dispatch({ type: ''USER_NAME_CLICKED'', payload: 123 })}>Robert</div> function* loadUserProfileOnNameClick() { yield* takeLatest("USER_NAME_CLICKED", fetchUser); } function* fetchUser(action) { try { const userProfile = yield fetch(`http://data.com/${action.payload.userId }`) yield put({ type: ''USER_PROFILE_LOADED'', userProfile }) } catch(err) { yield put({ type: ''USER_PROFILE_LOAD_FAILED'', err }) } }

Esta saga se traduce en:

cada vez que se hace clic en un nombre de usuario, busque el perfil del usuario y luego envíe un evento con el perfil cargado.

Como puede ver, hay algunas ventajas de redux-saga .

El uso de takeLatest permite expresar que solo está interesado en obtener los datos del último nombre de usuario en el que se hizo clic (manejar los problemas de concurrencia en caso de que el usuario haga clic muy rápido en muchos nombres de usuario). Este tipo de cosas es difícil con los thunks. Podría haber usado takeEvery si no desea este comportamiento.

Mantienes a los creadores de acción puros. Tenga en cuenta que aún es útil mantener actionCreators (en el dispatch componentes y put sagas), ya que podría ayudarlo a agregar validación de acciones (aserciones / flujo / mecanografiado) en el futuro.

Su código se vuelve mucho más comprobable ya que los efectos son declarativos

Ya no necesita activar llamadas de tipo rpc como actions.loadUser() . Su interfaz de usuario solo necesita enviar lo que HA SUCEDIDO. Solo disparamos eventos (¡siempre en tiempo pasado!) Y ya no acciones. Esto significa que puede crear "ducks" desacoplados o contextos limitados y que la saga puede actuar como el punto de acoplamiento entre estos componentes modulares.

Esto significa que sus vistas son más fáciles de administrar porque ya no necesitan contener esa capa de traducción entre lo que sucedió y lo que debería suceder como efecto

Por ejemplo, imagine una vista de desplazamiento infinita. CONTAINER_SCROLLED puede conducir a NEXT_PAGE_LOADED , pero ¿es realmente responsabilidad del contenedor desplazable decidir si debemos cargar o no otra página? Entonces, debe estar al tanto de cosas más complicadas, como si la última página se cargó correctamente o si ya hay una página que intenta cargarse, o si no quedan más elementos para cargar. No lo creo: para una reutilización máxima, el contenedor desplazable solo debe describir que se ha desplazado. La carga de una página es un "efecto comercial" de ese desplazamiento

Algunos podrían argumentar que los generadores pueden ocultar inherentemente el estado fuera de la tienda redux con variables locales, pero si comienzas a orquestar cosas complejas dentro de thunks iniciando temporizadores, etc., de todos modos, tendrías el mismo problema. Y hay un efecto de select que ahora permite obtener algún estado de su tienda Redux.

Las sagas pueden viajar en el tiempo y también permiten un registro de flujo complejo y herramientas de desarrollo en las que se está trabajando actualmente. Aquí hay algunos registros de flujo asíncrono simples que ya están implementados:

Desacoplamiento

Las sagas no solo están reemplazando los thunks redux. Vienen de backend / sistemas distribuidos / abastecimiento de eventos.

Es un error muy común pensar que las sagas están aquí para reemplazar sus redux thunks con una mejor capacidad de prueba. En realidad, esto es solo un detalle de implementación de redux-saga. El uso de efectos declarativos es mejor que los thunks para la comprobabilidad, pero el patrón de saga se puede implementar sobre el código imperativo o declarativo.

En primer lugar, la saga es una pieza de software que permite coordinar transacciones de larga duración (consistencia eventual) y transacciones en diferentes contextos delimitados (jerga de diseño dirigida por el dominio).

Para simplificar esto para el mundo frontend, imagine que hay widget1 y widget2. Cuando se hace clic en algún botón en widget1, entonces debería tener un efecto en widget2. En lugar de acoplar los 2 widgets (es decir, widget1 despacha una acción que se dirige a widget2), widget1 solo despacha que se hizo clic en su botón. Luego, la saga escuche este botón, haga clic y luego actualice widget2 presentando un nuevo evento que widget2 conoce.

Esto agrega un nivel de indirección que es innecesario para aplicaciones simples, pero hace que sea más fácil escalar aplicaciones complejas. Ahora puede publicar widget1 y widget2 en diferentes repositorios npm para que nunca tengan que conocerse, sin tener que compartir un registro global de acciones. Los 2 widgets ahora son contextos limitados que pueden vivir por separado. No se necesitan mutuamente para ser coherentes y también se pueden reutilizar en otras aplicaciones. La saga es el punto de acoplamiento entre los dos widgets que los coordinan de manera significativa para su negocio.

Algunos buenos artículos sobre cómo estructurar su aplicación Redux, en los que puede usar Redux-saga por motivos de desacoplamiento:

Un caso de uso concreto: sistema de notificación

Quiero que mis componentes puedan activar la visualización de notificaciones en la aplicación. Pero no quiero que mis componentes estén muy acoplados al sistema de notificación que tiene sus propias reglas comerciales (máximo 3 notificaciones mostradas al mismo tiempo, cola de notificaciones, tiempo de visualización de 4 segundos, etc.).

No quiero que mis componentes JSX decidan cuándo se mostrará / ocultará una notificación. Solo le doy la posibilidad de solicitar una notificación y dejar las complejas reglas dentro de la saga. Este tipo de cosas es bastante difícil de implementar con thunks o promesas.

He descrito here cómo se puede hacer esto con la saga

¿Por qué se llama una saga?

El término saga proviene del mundo del backend. Inicialmente presenté a Yassine (el autor de Redux-saga) a ese término en una larga discusión .

Inicialmente, ese término se introdujo con un paper , se suponía que el patrón de la saga se utilizaría para manejar la consistencia eventual en las transacciones distribuidas, pero su uso se ha extendido a una definición más amplia por parte de los desarrolladores de back-end para que ahora también cubra el "administrador de procesos" patrón (de alguna manera el patrón de saga original es una forma especializada de administrador de procesos).

Hoy, el término "saga" es confuso ya que puede describir 2 cosas diferentes. Como se usa en redux-saga, no describe una forma de manejar transacciones distribuidas sino más bien una forma de coordinar acciones en su aplicación. redux-saga también podría haberse llamado redux-process-manager .

Ver también:

Alternativas

Si no le gusta la idea de usar generadores pero está interesado en el patrón de saga y sus propiedades de desacoplamiento, también puede lograr lo mismo con redux-observable que usa el nombre epic para describir exactamente el mismo patrón, pero con RxJS. Si ya está familiarizado con Rx, se sentirá como en casa.

const loadUserProfileOnNameClickEpic = action$ => action$.ofType(''USER_NAME_CLICKED'') .switchMap(action => Observable.ajax(`http://data.com/${action.payload.userId}`) .map(userProfile => ({ type: ''USER_PROFILE_LOADED'', userProfile })) .catch(err => Observable.of({ type: ''USER_PROFILE_LOAD_FAILED'', err })) );

Algunos recursos útiles de redux-saga

2017 aconseja

  • No abuses de Redux-saga solo por usarlo. Las llamadas de API comprobables solo no valen la pena.
  • No elimine los thunks de su proyecto para la mayoría de los casos simples.
  • No dude en enviar thunks en yield put(someActionThunk) si tiene sentido.

Si tiene miedo de usar Redux-saga (o Redux-observable) pero solo necesita el patrón de desacoplamiento, marque redux-dispatch-subscribe : permite escuchar despachos y desencadenar nuevos despachos en el oyente.

const unsubscribe = store.addDispatchListener(action => { if (action.type === ''ping'') { store.dispatch({ type: ''pong'' }); } });


Hay creadores de acciones sincrónicas y luego hay creadores de acciones asincrónicas.

Un creador de acciones sincrónicas es aquel que, cuando lo llamamos, devuelve inmediatamente un objeto de Acción con todos los datos relevantes adjuntos a ese objeto y está listo para ser procesado por nuestros reductores.

Los creadores de acciones asincrónicas requieren de un poco de tiempo antes de estar listos para enviar una acción.

Por definición, cada vez que tenga un creador de acciones que realice una solicitud de red, siempre calificará como creador de acciones asíncronas.

Si desea tener creadores de acciones asincrónicas dentro de una aplicación Redux, debe instalar algo llamado middleware que le permitirá tratar con esos creadores de acciones asincrónicas.

Puede verificar esto en el mensaje de error que nos dice que usemos middleware personalizado para acciones asíncronas.

Entonces, ¿qué es un middleware y por qué lo necesitamos para el flujo asíncrono en Redux?

En el contexto de middleware redux como redux-thunk, un middleware nos ayuda a tratar con creadores de acciones asíncronas, ya que eso es algo que Redux no puede manejar de inmediato.

Con un middleware integrado en el ciclo de Redux, todavía estamos llamando a los creadores de acciones, que devolverán una acción que se enviará, pero ahora cuando despachemos una acción, en lugar de enviarla directamente a todos nuestros reductores, vamos para decir que se enviará una acción a través de todos los middleware diferentes dentro de la aplicación.

Dentro de una sola aplicación Redux, podemos tener tantos o tan pocos middleware como queramos. En su mayor parte, en los proyectos en los que trabajamos tendremos uno o dos middleware conectados a nuestra tienda Redux.

Un middleware es una función de JavaScript simple que se llamará con cada acción que despachemos. Dentro de esa función, un middleware tiene la oportunidad de evitar que una acción se envíe a cualquiera de los reductores, puede modificar una acción o simplemente perder el tiempo con una acción de cualquier manera que, por ejemplo, podríamos crear un middleware que registre la consola cada acción que envíes solo para tu placer visual.

Hay una gran cantidad de middleware de código abierto que puede instalar como dependencias en su proyecto.

No está limitado a utilizar solo middleware de código abierto o instalarlos como dependencias. Puede escribir su propio middleware personalizado y usarlo dentro de su tienda Redux.

Uno de los usos más populares del middleware (y obtener su respuesta) es tratar con creadores de acciones asincrónicas, probablemente el middleware más popular que existe es redux-thunk y se trata de ayudarlo a lidiar con creadores de acciones asincrónicas.

Existen muchos otros tipos de middleware que también lo ayudan a tratar con creadores de acciones asincrónicas.


Para responder la pregunta:

¿Por qué el componente contenedor no puede llamar a la API asíncrona y luego enviar las acciones?

Yo diría por al menos dos razones:

La primera razón es la separación de inquietudes, no es el trabajo de action creator llamar api y recuperar datos, debe pasar dos argumentos a su action creator function , el action type y a payload .

La segunda razón es porque redux store está esperando un objeto simple con el tipo de acción obligatoria y opcionalmente un payload (pero aquí también tiene que pasar la carga útil).

El creador de la acción debe ser un objeto simple como el siguiente:

function addTodo(text) { return { type: ADD_TODO, text } }

Y el trabajo de Redux-Thunk midleware que dispache el resultado de su api call a la apropiada action .


Usar Redux-saga es el mejor middleware en la implementación de React-redux.

Ej: store.js

import createSagaMiddleware from ''redux-saga''; import { createStore, applyMiddleware } from ''redux''; import allReducer from ''../reducer/allReducer''; import rootSaga from ''../saga''; const sagaMiddleware = createSagaMiddleware(); const store = createStore( allReducer, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga); export default store;

Y luego saga.js

import {takeLatest,delay} from ''redux-saga''; import {call, put, take, select} from ''redux-saga/effects''; import { push } from ''react-router-redux''; import data from ''./data.json''; export function* updateLesson(){ try{ yield put({type:''INITIAL_DATA'',payload:data}) // initial data from json yield* takeLatest(''UPDATE_DETAIL'',updateDetail) // listen to your action.js } catch(e){ console.log("error",e) } } export function* updateDetail(action) { try{ //To write store update details } catch(e){ console.log("error",e) } } export default function* rootSaga(){ yield [ updateLesson() ] }

Y luego action.js

export default function updateFruit(props,fruit) { return ( { type:"UPDATE_DETAIL", payload:fruit, props:props } ) }

Y luego reducer.js

import {combineReducers} from ''redux''; const fetchInitialData = (state=[],action) => { switch(action.type){ case "INITIAL_DATA": return ({type:action.type, payload:action.payload}); break; } return state; } const updateDetailsData = (state=[],action) => { switch(action.type){ case "INITIAL_DATA": return ({type:action.type, payload:action.payload}); break; } return state; } const allReducers =combineReducers({ data:fetchInitialData, updateDetailsData }) export default allReducers;

Y luego main.js

import React from ''react''; import ReactDOM from ''react-dom''; import App from ''./app/components/App.jsx''; import {Provider} from ''react-redux''; import store from ''./app/store''; import createRoutes from ''./app/routes''; const initialState = {}; const store = configureStore(initialState, browserHistory); ReactDOM.render( <Provider store={store}> <App /> /*is your Component*/ </Provider>, document.getElementById(''app''));

prueba esto ... está funcionando


El objetivo de Abramov, y todos lo ideal, es simplemente encapsular la complejidad (y las llamadas asíncronas) en el lugar donde sea más apropiado .

¿Dónde está el mejor lugar para hacer eso en el flujo de datos estándar de Redux? Qué tal si:

  • Reductores ? De ninguna manera. Deben ser funciones puras sin efectos secundarios. Actualizar la tienda es un asunto serio y complicado. No lo contamines.
  • Componentes tontos de la vista? Definitivamente No. Tienen una preocupación: presentación e interacción con el usuario, y deben ser lo más simples posible.
  • Componentes del contenedor? Posible, pero subóptimo. Tiene sentido porque el contenedor es un lugar donde encapsulamos cierta complejidad relacionada con la vista e interactuamos con la tienda, pero:
    • Los contenedores deben ser más complejos que los componentes tontos, pero sigue siendo una responsabilidad única: proporcionar enlaces entre la vista y el estado / tienda. Su lógica asíncrona es una preocupación completamente diferente de eso.
    • Al colocarlo en un contenedor, estaría bloqueando su lógica asíncrona en un solo contexto, para una sola vista / ruta. Mala idea. Idealmente, todo es reutilizable y está totalmente desacoplado.
  • ¿S u otro módulo de servicio? Mala idea: necesitarías inyectar acceso a la tienda, lo cual es una pesadilla de mantenibilidad / comprobabilidad. Es mejor seguir la corriente de Redux y acceder a la tienda solo usando las API / modelos provistos.
  • ¿Las acciones y los middlewares que las interpretan? ¡¿Por qué no?! Para empezar, es la única opción importante que nos queda. :-) Más lógicamente, el sistema de acción es una lógica de ejecución desacoplada que puede usar desde cualquier lugar. Tiene acceso a la tienda y puede enviar más acciones. Tiene una responsabilidad única, que es organizar el flujo de control y datos alrededor de la aplicación, y la mayoría de los asíncronos encajan perfectamente en eso.
    • ¿Qué pasa con los creadores de acción? ¿Por qué no simplemente sincronizar allí, en lugar de hacerlo en las acciones mismas y en Middleware?
      • Primero y más importante, los creadores no tienen acceso a la tienda, como lo hace el middleware. Eso significa que no puede enviar nuevas acciones contingentes, no puede leer desde la tienda para componer su asíncrono, etc.
      • Por lo tanto, mantenga la complejidad en un lugar que sea complejo por necesidad y mantenga todo lo demás simple. Los creadores pueden ser funciones simples, relativamente puras y fáciles de probar.

Para responder la pregunta que se hace al principio:

¿Por qué el componente contenedor no puede llamar a la API asíncrona y luego enviar las acciones?

Tenga en cuenta que esos documentos son para Redux, no para Redux más React. Las tiendas Redux conectadas a los componentes React pueden hacer exactamente lo que usted dice, pero una tienda Plain Jane Redux sin middleware no acepta argumentos para dispatch excepto objetos simples.

Sin middleware, por supuesto, aún podría hacerlo

const store = createStore(reducer); MyAPI.doThing().then(resp => store.dispatch(...));

Pero es un caso similar donde la asincronía se envuelve alrededor de Redux en lugar de ser manejada por Redux. Entonces, el middleware permite la asincronía modificando lo que se puede pasar directamente al dispatch .

Dicho esto, el espíritu de su sugerencia es, creo, válido. Ciertamente, hay otras formas de manejar la asincronía en una aplicación Redux + React.

Una ventaja de usar middleware es que puedes seguir usando creadores de acción de manera normal sin preocuparte exactamente de cómo están conectados. Por ejemplo, usando redux-thunk , el código que escribió se parecería mucho a

function updateThing() { return dispatch => { dispatch({ type: ActionTypes.STARTED_UPDATING }); AsyncApi.getFieldValue() .then(result => dispatch({ type: ActionTypes.UPDATED, payload: result })); } } const ConnectedApp = connect( (state) => { ...state }, { update: updateThing } )(App);

que no se ve tan diferente del original, simplemente se baraja un poco, y connect no sabe que updateThing es (o necesita ser) asíncrono.

Si también desea apoyar promises , observables , sagas o creadores de acción personalizados y altamente declarativos , Redux puede hacerlo simplemente cambiando lo que pasa para dispatch (también conocido como lo que devuelve de los creadores de acción). No es necesario muckear con los componentes React (o connect llamadas).


La respuesta corta : me parece un enfoque totalmente razonable para el problema de la asincronía. Con un par de advertencias.

Tenía una línea de pensamiento muy similar cuando trabajaba en un nuevo proyecto que recién comenzamos en mi trabajo. Era un gran admirador del elegante sistema de Vanilla Redux para actualizar la tienda y volver a entregar los componentes de una manera que se mantiene fuera de las entrañas de un árbol de componentes React. Me pareció extraño conectarme a ese elegante mecanismo de dispatch para manejar la asincronía.

Terminé con un enfoque realmente similar a lo que tienes allí en una biblioteca que factoré a partir de nuestro proyecto, que llamamos react-redux-controller .

Terminé sin seguir el enfoque exacto que tienes arriba por un par de razones:

  1. De la forma en que lo tiene escrito, esas funciones de despacho no tienen acceso a la tienda. Puede evitarlo haciendo que sus componentes de la interfaz de usuario pasen toda la información que necesita la función de envío. Pero diría que esto combina esos componentes de la interfaz de usuario con la lógica de envío innecesariamente. Y lo que es más problemático, no hay una forma obvia para que la función de despacho acceda al estado actualizado en las continuaciones asíncronas.
  2. Las funciones de despacho tienen acceso para dispatch a través del ámbito léxico. Esto limita las opciones para refactorizar una vez que la declaración de connect se salga de control, y se ve bastante difícil de manejar con ese único método de update . Por lo tanto, necesita algún sistema que le permita componer esas funciones de despachador si las divide en módulos separados.

En conjunto, debe armar algún sistema para permitir que el dispatch y la tienda se inyecten en sus funciones de despacho, junto con los parámetros del evento. Sé de tres enfoques razonables para esta inyección de dependencia:

  • redux-thunk hace esto de una manera funcional, pasándolos a sus thunks (haciéndolos no exactamente thunks en absoluto, según las definiciones de domo). No he trabajado con los otros enfoques de middleware de dispatch , pero supongo que son básicamente los mismos.
  • react-redux-controller hace esto con una rutina. Como beneficio adicional, también le da acceso a los "selectores", que son las funciones que puede haber pasado como primer argumento para connect , en lugar de tener que trabajar directamente con la tienda sin procesar y normalizada.
  • También podría hacerlo orientado a objetos al inyectarlos en this contexto, a través de una variedad de mecanismos posibles.

Actualizar

Se me ocurre que parte de este enigma es una limitación de react-redux . El primer argumento para connect obtiene una instantánea del estado, pero no el envío. El segundo argumento se despacha pero no el estado. Ninguno de los argumentos obtiene un thunk que se cierra sobre el estado actual, por poder ver el estado actualizado en el momento de una continuación / devolución de llamada.


OK, comencemos a ver cómo funciona el middleware primero, eso responde bastante a la pregunta, este es el código fuente de una función pplyMiddleWare en Redux:

function applyMiddleware() { for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { middlewares[_key] = arguments[_key]; } return function (createStore) { return function (reducer, preloadedState, enhancer) { var store = createStore(reducer, preloadedState, enhancer); var _dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: function dispatch(action) { return _dispatch(action); } }; chain = middlewares.map(function (middleware) { return middleware(middlewareAPI); }); _dispatch = compose.apply(undefined, chain)(store.dispatch); return _extends({}, store, { dispatch: _dispatch }); }; }; }

Mire esta parte, vea cómo nuestro despacho se convierte en una función .

... getState: store.getState, dispatch: function dispatch(action) { return _dispatch(action); }

  • Tenga en cuenta que cada middleware recibirá las funciones dispatch y getState como argumentos con nombre.

OK, así es como se presenta Redux-thunk como uno de los middlewares más utilizados para Redux:

El middleware Redux Thunk le permite escribir creadores de acciones que devuelven una función en lugar de una acción. El thunk se puede usar para retrasar el envío de una acción, o para enviar solo si se cumple una determinada condición. La función interna recibe el envío de métodos de almacenamiento y getState como parámetros.

Entonces, como puede ver, devolverá una función en lugar de una acción, lo que significa que puede esperar y llamarla cuando lo desee, ya que es una función ...

Entonces, ¿qué diablos es thunk? Así se introdujo en Wikipedia:

En la programación de computadoras, un thunk es una subrutina utilizada para inyectar un cálculo adicional en otra subrutina. Los thunks se usan principalmente para retrasar un cálculo hasta que sea necesario, o para insertar operaciones al principio o al final de la otra subrutina. Tienen una variedad de otras aplicaciones para compilar la generación de código y en la programación modular.

El término se originó como un derivado jocoso de "pensar".

Un thunk es una función que envuelve una expresión para retrasar su evaluación.

//calculation of 1 + 2 is immediate //x === 3 let x = 1 + 2; //calculation of 1 + 2 is delayed //foo can be called later to perform the calculation //foo is a thunk! let foo = () => 1 + 2;

Entonces, vea cuán fácil es el concepto y cómo puede ayudarlo a administrar sus acciones asíncronas ...

Eso es algo que puedes vivir sin él, pero recuerda que en la programación siempre hay formas mejores, más ordenadas y adecuadas de hacer las cosas ...