Comment envoyer L'action Redux d'un composant apatride lorsque le trajet est chargé?

Goal : lors du chargement d'une route react-routeur, envoyer une action Redux demandant asynchrone Saga travailleur pour récupérer des données pour la composante apatride sous-jacente de cette route.

problème : les composants apatrides sont de simples fonctions et n'ont pas de méthodes de cycle de vie, comme componentDidMount, donc je ne peux pas(?) lancer L'action Redux de l'intérieur de la fonction.

ma question est en partie liée à conversion du composant Stateful React au composant fonctionnel apatride: comment mettre en œuvre le type de fonctionnalité" componentDidMount"? , mais mon but est simplement d'envoyer une seule action Redux demandant que les données soient peuplées au magasin de manière asynchrone (J'utilise Saga, mais je pense que ce n'est pas pertinent pour le problème, car mon but est simplement d'envoyer une action Redux ordinaire), après quoi le composant apatride se renverrait en raison du changement de données prop.

je pense à deux approches: soit utiliser une caractéristique de react-router , ou la méthode connect de Redux. Est-il un soi-disant "Réagir" pour accomplir mon objectif?

EDIT: la seule solution que j'ai trouvée jusqu'à présent, est d'envoyer l'action à l'intérieur de mapDispatchToProps, de cette façon:

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);

cependant, cela semble un peu sale et pas la bonne façon.

21
demandé sur Community 2016-09-29 07:31:06

4 réponses

Je ne sais pas pourquoi vous voulez absolument un composant apatride, alors qu'un composant stateful avec componentDidMount ferait le travail d'une manière simple.

actions de répartition dans mapDispatchToProps est très dangereux et peut conduire à l'expédition non seulement sur le montage, mais chaque fois que les propres gouttes ou des accessoires de stockage change. Les effets secondaires ne sont pas censés être fait dans cette méthode reste pur.

Un moyen facile de garder votre composant apatrides est d'envelopper il en un HOC (composant D'ordre supérieur) que vous pourriez facilement créer:

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

notez que si vous utilisez Redux connect après cette HOC , vous pouvez facilement accéder à la dispatch depuis props directement comme si vous n'utilisiez pas mapDispatchToProps , la dispatch est injectée.

vous pouvez alors faire quelque chose de très simple comme:

let MyStatelessComponent = ...

MyStatelessComponent = withLifecycle({
   componentDidMount: () => this.props.dispatch({ type: myActionTypes.DATA_GET_REQUEST });
})(MyStatelessComponent)

export default connect(state => ({
   date: state.myReducer.data
}))(MyStatelessComponent);

définition spéciale:

import { createClass } from 'react';

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

Voici une simple mise en œuvre de ce que vous pourriez faire:

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 vous utilisez connect autour de Hello component, vous pouvez injecter dispatch comme accessoire et l'utiliser à la place d'un message d'alerte.

JsFiddle

12
répondu Sebastien Lorber 2018-01-06 22:13:49

je pense que j'ai trouvé la solution la plus propre sans avoir à utiliser des composants stateful:

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 solution passe la mémoire à une fonction d'ordre supérieur qui est passée à la méthode du cycle de vie onEnter. J'ai trouvé la solution de https://github.com/reactjs/react-router-redux/issues/319

4
répondu Kitanotori 2016-09-29 13:06:43

si vous voulez qu'il soit complètement apatride, vous pouvez envoyer un événement lorsque la route est entrée en utilisant OnEnter event.

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

Maintenant, vous pouvez écrire votre fonction ici pourvu que vous importiez l'expédition dans ce fichier ou que vous la passiez comme paramètre.

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

Mais cette solution me semble encore plus sale.

l'autre solution que je pourrais être vraiment efficace est d'utiliser les conteneurs et les composants d'appel ont été intégrés là-dedans.

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)
1
répondu Harkirat Saluja 2016-09-29 07:18:35

en général, je ne pense pas que cela soit possible sans une action de déclenchement qui est déclenchée lorsque le composant est monté/rendu pour la première fois. Vous avez réalisé ceci en rendant mapDispatchToProps impur. Je suis entièrement d'accord avec Sébastien pour dire que c'est une mauvaise idée. Vous pourriez aussi déplacer l'impureté vers la fonction de rendu, ce qui est encore pire. Les méthodes de cycle de vie des composants sont conçues pour cela! Sa solution ponctuelle a du sens, si vous ne voulez pas avoir à écrire classes de composants.

Je n'ai pas grand chose à ajouter, mais au cas où vous vouliez juste voir le code réel de la saga, voici un certain pseudocode, compte tenu d'une telle action de déclenchement (non testé):

// 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 {};
};

plus le boilerplate:

const sagaMiddleware = createSagaMiddleware();

export default connect(mapStateToProps, mapDispatchToProps)(MyStatelessComponent);

const store = createStore(
  myReducer,
  { whatever: {loading: true, data: null} },
  applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mainSaga)
1
répondu WuTheFWasThat 2016-10-03 00:59:49