7 votes

Répartir les actions comme il se doit

S'il vous plaît, vérifiez le Editar

J'essaie de mettre en place des sagas dans mon application.

En ce moment, je vais chercher les accessoires d'une très mauvaise façon. Mon application consiste principalement à interroger des données provenant d'autres sources.

Actuellement, voici comment fonctionne mon application :

J'ai conteneurs qui ont mapStateToProps, mapDispatchToProps.

const mapStateToProps = state => {
  return {
    someState: state.someReducer.someReducerAction,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators({someAction, someOtherAction, ...}, dispatch)
};

const something = drizzleConnect(something, mapStateToProps, mapDispatchToProps);

export default something;

et ensuite, j'ai actions comme ceci :

import * as someConstants from '../constants/someConstants';

export const someFunc = (someVal) => (dispatch) => {
    someVal.methods.someMethod().call().then(res => {
        dispatch({
            type: someConstants.FETCH_SOMETHING,
            payload: res
        })

    })
}

et réducteurs comme celui ci-dessous :

export default function someReducer(state = INITIAL_STATE, action) {
    switch (action.type) {
        case types.FETCH_SOMETHING:
            return ({
                ...state,
                someVar: action.payload
            });

Je combine les réducteurs avec le combineReducers de redux et les exporte en tant que réducteur unique, que j'importe ensuite dans mon magasin.

Parce que j'utilise drizzle, ma rootSaga est la suivante :

import { all, fork } from 'redux-saga/effects'
import { drizzleSagas } from 'drizzle'

export default function* root() {
  yield all(
    drizzleSagas.map(saga => fork(saga)),
  )
}

Donc, maintenant, lorsque je veux mettre à jour les accessoires, dans la section componentWillReceiveProps du composant, je le fais : this.props.someAction()

Ok, ça marche, mais je sais que ce n'est pas la bonne méthode. En fait, c'est la pire chose que je puisse faire.

Donc, maintenant, ce que je pense que je devrais faire :

Créer des sagas distinctes, que je vais ensuite importer dans le fichier rootSaga. Ces sagas vont interroger les sources à des intervalles prédéfinis et mettre à jour les props si nécessaire.

Mais mon problème est de savoir comment ces sagas devraient être écrites.

Est-il possible que vous puissiez me donner un exemple, basé sur les actions, les réducteurs et les conteneurs que j'ai mentionnés ci-dessus ?

Edit :

J'ai réussi à suivre les instructions d'Apachuilo.

Jusqu'à présent, j'ai fait ces ajustements :

Le site actions sont comme ça :

export const someFunc = (payload, callback) => ({
            type: someConstants.FETCH_SOMETHING_REQUEST,
            payload,
            callback
})

et le réducteurs comme ceci :

export default function IdentityReducer(state = INITIAL_STATE, {type, payload}) {
    switch (type) {
        case types.FETCH_SOMETHING_SUCCESS:
            return ({
                ...state,
                something: payload,
            });
...

J'ai également créé certainesSagas :

...variousImports

import * as apis from '../apis/someApi'

function* someHandler({ payload }) {
    const response = yield call(apis.someFunc, payload)

    response.data
        ? yield put({ type: types.FETCH_SOMETHING_SUCCESS, payload: response.data })
        : yield put({ type: types.FETCH_SOMETHING_FAILURE })
}

export const someSaga = [
    takeLatest(
        types.FETCH_SOMETHING_REQUEST,
        someHandler
    )
]

et ensuite, mis à jour le rootSaga :

import { someSaga } from './sagas/someSagas'

const otherSagas = [
  ...someSaga,
]

export default function* root() {
  yield all([
    drizzleSagas.map(saga => fork(saga)),
    otherSagas
  ])
}

De plus, l'api est la suivante :

export const someFunc = (payload) => {
    payload.someFetching.then(res => {
        return {data: res}
    }) //returns 'data' of undefined but just "return {data: 'something'} returns that 'something'

Donc, j'aimerais mise à jour mes questions :

  1. Mes API sont dépendantes de l'état du magasin. Comme vous pouvez le comprendre, je suis en train de construire une dApp. Donc, Drizzle (un middleware que j'utilise afin de d'accéder à la blockchain), doit être lancé avant que j'appelle les APIs et de renvoyer des informations aux composants. Ainsi,

    a. En essayant de lire l'état avec getState(), j'obtiens des contrats vides (contrats qui ne sont pas encore "prêts"). (contrats qui ne sont pas encore "prêts") - je ne peux donc pas récupérer l'info - je Je n'aime pas lire l'état depuis le magasin, mais...

    b. Passer l'état à travers le composant (this.props.someFunc(someState), me retourne Cannot read property 'data' of undefined Ce qui est amusant, c'est que je peux consoler.log l'état l'état (il semble correct) et en essayant de simplement `retourner {data : 'someData'}, les props reçoivent les données.

  2. Dois-je exécuter this.props.someFunc() sur, par exemple, componentWillMount() ? Est-ce la bonne façon de mettre à jour les props ?

Désolé pour ce très long message, mais je voulais être précis.

Modifier pour 1b : Uhh, tant de modifications :) J'ai résolu le problème de la résolution indéfinie. Il fallait juste écrire l'API comme ceci :

export function someFunc(payload)  {

    return payload.someFetching.then(res => {
            return ({ data: res })   
    }) 
}

1voto

apachuilo Points 333

Je ne veux pas imposer le modèle que j'utilise, mais je l'ai utilisé avec succès pendant un certain temps dans plusieurs applications (tout commentaire de votre part est le bienvenu). Le mieux est de lire et d'expérimenter pour trouver ce qui fonctionne le mieux pour vous et vos projets.

Voici un article utile que j'ai lu lorsque j'ai trouvé ma solution. Il y en avait un autre, et si je peux le trouver, je l'ajouterai ici.

https://medium.com/@TomasEhrlich/redux-saga-factories-and-decorators-8dd9ce074923

C'est la configuration de base que j'utilise pour les projets. Veuillez noter que j'utilise un fichier utilitaire saga. Je fournis cependant un exemple d'utilisation sans ce fichier. Il se peut que vous ayez à créer quelque chose en cours de route pour vous aider à réduire ce gabarit. (peut-être même quelque chose pour vous aider à gérer votre scénario de sondage).

Je déteste tellement les modèles passe-partout. J'ai même créé un outil que j'utilise avec mes API golang pour générer automatiquement une partie de ce texte passe-partout en parcourant les endpoints swagger doc/router.

Edit : Ajout d'un exemple de conteneur.

exemple de composant

import React, { Component } from 'react'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { getResource } from '../actions/resource'

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      getResource
    },
    dispatch
  )

class Example extends Component {
  handleLoad = () => {
    this.props.getResource({
      id: 1234
    })
  }

  render() {
    return <button onClick={this.handleLoad}>Load</button>
  }
}

export default connect(
  null,
  mapDispatchToProps
)(Example)

exemple action/resource.js

import { useDispatch } from 'react-redux'

const noop = () => {}
const empty = []

export const GET_RESOURCE_REQUEST = 'GET_RESOURCE_REQUEST'
export const getResource = (payload, callback) => ({
  type: GET_RESOURCE_REQUEST,
  payload,
  callback,
})

// I use this for projects with hooks!
export const useGetResouceAction = (callback = noop, deps = empty) => {
  const dispatch = useDispatch()

  return useCallback(
    payload =>
      dispatch({ type: GET_RESOURCE_REQUEST, payload, callback }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [dispatch, ...deps]
  )
}

Un fichier d'action redux assez basique.

exemple reducers/resource.js

export const GET_RESOURCE_SUCCESS = 'GET_RESOURCE_SUCCESS'

const initialState = {
  resouce: null
}

export default (state = initialState, { type, payload }) => {
  switch (type) {
    case GET_RESOURCE_SUCCESS: {
      return {
        ...state,
        resouce: payload.Data,
      }
    }
}

Modèle de réducteur assez standard - Notez l'utilisation de _SUCCESS au lieu de _REQUEST ici. C'est important.

exemple saga/resouce.js

import { takeLatest } from 'redux-saga/effects'

import { GET_RESOUCE_REQUEST } from '../actions/resource'

// need if not using the util
import { GET_RESOURCE_SUCCESS } from '../reducers/resource'

import * as resouceAPI from '../api/resource'

import { composeHandlers } from './sagaHandlers'

// without the util
function* getResourceHandler({ payload }) {
    const response = yield call(resouceAPI.getResouce, payload);

    response.data
      ? yield put({ type: GET_RESOURCE_SUCCESS, payload: response.data })
      : yield put({
          type: "GET_RESOURCE_FAILURE"
        });
  }

export const resourceSaga = [
  // Example that uses my util
  takeLatest(
    GET_RESOUCE_REQUEST,
    composeHandlers({
      apiCall: resouceAPI.getResouce
    })
  ),
  // Example without util
  takeLatest(
    GET_RESOUCE_REQUEST,
    getResourceHandler
  )
]

Exemple de fichier saga pour une ressource donnée. C'est ici que je connecte l'appel api avec l'appel reducer dans un tableau par endpoint pour la ressource. Ceci est ensuite réparti sur la saga Root. Parfois vous pouvez vouloir utiliser takeEvery au lieu de takeLatest -- tout dépend du cas d'utilisation.

exemple saga/index.js

import { all } from 'redux-saga/effects'

import { resourceSaga } from './resource'

export const sagas = [
  ...resourceSaga,
]

export default function* rootSaga() {
  yield all(sagas)
}

Simple saga de Root, ressemble un peu à un réducteur de Root.

util saga/sagaHandlers.js

export function* apiRequestStart(action, apiFunction) {
  const { payload } = action

  let success = true
  let response = {}
  try {
    response = yield call(apiFunction, payload)
  } catch (e) {
    response = e.response
    success = false
  }

  // Error response
  // Edit this to fit your needs
  if (typeof response === 'undefined') {
    success = false
  }

  return {
    action,
    success,
    response,
  }
}

export function* apiRequestEnd({ action, success, response }) {
  const { type } = action
  const matches = /(.*)_(REQUEST)/.exec(type)
  const [, requestName] = matches

  if (success) {
    yield put({ type: `${requestName}_SUCCESS`, payload: response })
  } else {
    yield put({ type: `${requestName}_FAILURE` })
  }

  return {
    action,
    success,
    response,
  }
}

// External to redux saga definition -- used inside components
export function* callbackHandler({ action, success, response }) {
  const { callback } = action
  if (typeof callback === 'function') {
    yield call(callback, success, response)
  }

  return action
}

export function* composeHandlersHelper(
  action,
  {
    apiCall = () => {}
  } = {}
) {
  const { success, response } = yield apiRequestStart(action, apiCall)

  yield apiRequestEnd({ action, success, response })

  // This callback handler is external to saga
  yield callbackHandler({ action, success, response })
}

export function composeHandlers(config) {
  return function*(action) {
    yield composeHandlersHelper(action, config)
  }
}

Ceci est une version très abrégée de ma saga util handler. Cela peut être très difficile à digérer. Si vous voulez la version complète, je verrai ce que je peux faire. La version complète gère des choses comme la génération automatique de toast en cas de succès/erreur de l'api et le rechargement de certaines ressources en cas de succès. J'ai quelque chose pour gérer les téléchargements de fichiers. Et une autre chose pour gérer toute logique interne bizarre qui pourrait avoir lieu (rarement utilisé).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X