react example connectedrouter connected reactjs react-redux react-router-redux redux-saga

reactjs - example - ¿Cómo despachar la acción de Redux desde el componente sin estado cuando se carga la ruta?



react router dom redux (4)

Objetivo : al cargar una ruta de enrutador de reacción, despache una acción de Redux solicitando al trabajador asíncrónico de Saga que busque datos para el componente sin estado subyacente de esa ruta.

Problema : los componentes sin estado son meras funciones y no tienen métodos de ciclo de vida, como componentDidMount, así que no puedo (?) Enviar una acción de Redux desde dentro de la función.

Mi pregunta está relacionada en parte con la conversión del componente React con estado a un componente funcional sin estado: ¿Cómo implementar el tipo de funcionalidad "componentDidMount"? , pero mi objetivo es simplemente enviar una sola acción de Redux solicitando que los datos se rellenen de forma asíncrona (uso Saga, pero creo que eso es irrelevante para el problema, ya que mi objetivo es simplemente enviar una acción Redux normal) el componente sin estado se volvería a generar debido a la proposición de datos modificada.

Estoy pensando en dos enfoques: usar alguna característica de react-router o el método de connect de Redux. ¿Existe un llamado "modo de reacción" para lograr mi objetivo?

EDITAR: la única solución que he encontrado hasta ahora, es enviar la acción dentro de mapDispatchToProps, de esta manera:

const mapStateToProps = (state, ownProps) => ({ data: state.myReducer.data // data rendered by the stateless component }); const mapDispatchToProps = (dispatch) => { // catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store dispatch({ type: myActionTypes.DATA_GET_REQUEST }); return {}; }; export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);

Sin embargo, esto parece de alguna manera sucio y no es la forma correcta.


Creo que encontré la solución más limpia sin tener que usar componentes con estado:

const onEnterAction = (store, dispatchAction) => { return (nextState, replace) => { store.dispatch(dispatchAction()); }; }; const myDataFetchAction = () => ({ type: DATA_GET_REQUEST }); export const Routes = (store) => ( <Route path=''/'' component={MyStatelessComponent} onEnter={onEnterAction(store, myDataFetchAction)}/> );

La solución pasa la tienda a una función de orden superior que se pasa al método de ciclo de vida onEnter. Encontró la solución en https://github.com/reactjs/react-router-redux/issues/319


En general, no creo que esto sea posible sin algún tipo de acción de activación que se distribuye cuando el componente se monta / renderiza por primera vez. Has logrado esto haciendo impuro a mapDispatchToProps. Estoy 100% de acuerdo con Sebastien en que es una mala idea. También puede mover la impureza a la función de renderización, que es aún peor. ¡Los métodos del ciclo de vida de los componentes están diseñados para esto! Su solución HOC tiene sentido, si no desea tener que escribir las clases de componentes.

No tengo mucho que agregar, pero en caso de que solo quisieras ver el código de la saga real, aquí tienes un pseudocódigo, dada una acción de activación (no probada):

// takes the request, *just a single time*, fetch data, and sets it in state function* loadDataSaga() { yield take(myActionTypes.DATA_GET_REQUEST) const data = yield call(fetchData) yield put({type: myActionTypes.SET_DATA, data}) } function* mainSaga() { yield fork(loadDataSaga); ... do all your other stuff } function myReducer(state, action) { if (action.type === myActionTypes.SET_DATA) { const newState = _.cloneDeep(state) newState.whatever.data = action.data newState.whatever.loading = false return newState } else if ( ... ) { ... blah blah } return state } const MyStatelessComponent = (props) => { if (props.loading) { return <Spinner/> } return <some stuff here {...props.data} /> } const mapStateToProps = (state) => state.whatever; const mapDispatchToProps = (dispatch) => { // catched by a Saga watcher, and further delivered to a Saga worker that asynchronically fetches data to the store dispatch({ type: myActionTypes.DATA_GET_REQUEST }); return {}; };

más el repetitivo:

const sagaMiddleware = createSagaMiddleware(); export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent); const store = createStore( myReducer, { whatever: {loading: true, data: null} }, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(mainSaga)


No sé por qué absolutamente desea un componente sin estado, mientras que un componente con estado con componentDidMount haría el trabajo de una manera simple.

Las acciones de despacho en mapDispatchToProps son muy peligrosas y pueden llevar al despacho no solo en el montaje, sino siempre que se modifiquen los Props o los accesorios de la tienda. No se espera que los efectos secundarios se hagan en este método que debería permanecer puro.

Una manera fácil de mantener su componente sin estado es envolverlo en un HOC (componente de orden superior) que podría crear fácilmente:

MyStatelessComponent = withLifecycleDispatch(dispatch => ({ componentDidMount: function() { dispatch({ type: myActionTypes.DATA_GET_REQUEST })}; }))(MyStatelessComponent)

Tenga en cuenta que si usa Redux connect después de este HOC, puede acceder fácilmente al envío desde los accesorios directamente como si no usara mapDispatchToProps , el envío se inyecta.

Entonces puedes hacer algo muy simple como:

let MyStatelessComponent = ... MyStatelessComponent = withLifecycle({ componentDidMount: () => this.props.dispatch({ type: myActionTypes.DATA_GET_REQUEST }); })(MyStatelessComponent) export default connect(state => ({ date: state.myReducer.data }))(MyStatelessComponent);

Definición de HOC:

import { createClass } from ''react''; const withLifeCycle = (spec) => (BaseComponent) => { return createClass({ ...spec, render() { return BaseComponent(); } }) }

Aquí hay una implementación simple de lo que podrías hacer:

const onMount = (onMountFn) => (Component) => React.createClass({ componentDidMount() { onMountFn(this.props); }, render() { return <Component {...this.props} /> } }); let Hello = (props) => ( <div>Hello {props.name}</div> ) Hello = onMount((mountProps) => { alert("mounting, and props are accessible: name=" + mountProps.name) })(Hello)

Si usa el componente de connect alrededor de Hello, puede inyectar el envío como accesorios y usarlo en lugar de un mensaje de alerta.

JsFiddle


Si desea que sea completamente sin estado, puede enviar un evento cuando la ruta se ingresa usando el evento onEnter.

<Route to=''/app'' Component={App} onEnter={dispatchAction} />

Ahora puede escribir su función aquí siempre que importe el envío en este archivo o de alguna manera lo pase como parámetro.

function dispatchAction(nexState,replace){ //dispatch }

Pero esta solución que siento es aún más sucia.

La otra solución que podría ser realmente eficiente es usar contenedores y llamar a componentDidMount en eso.

import React,{Component,PropTypes} from ''react'' import {connect} from ''react-redux'' const propTypes = { // } function mapStateToProps(state){ // } class ComponentContainer extends Component { componentDidMount(){ //dispatch action } render(){ return( <Component {...this.props}/> //your dumb/stateless component . Pass data as props ) } } export default connect(mapStateToProps)(ComponentContainer)