Dva: Comment annuler les effets

Créé le 8 juin 2018  ·  11Commentaires  ·  Source: dvajs/dva

Code pour reproduire le problème : (veuillez fournir un code ou des étapes reproductibles)

Scénario : S'il y a deux pages A et B dans le projet, correspondant au modèle A et au modèle B, il y a des requêtes réseau asynchrones dans chaque page, qui sont initiées dans les effets et les données renvoyées sont écrites dans le modèle actuel via le réducteur Etat.

Lorsque je quitte la page A ou la page B, je souhaite effacer les données dans le modèle correspondant afin de ne pas devenir des données sales lorsque je les réutiliserai la prochaine fois.
J'ai écrit un réducteur clair dans le modèle, dans la méthode componentWillUnmount
dispatch({type:${model.namespace}/clear})

Comportement attendu : (effet normal attendu)

L'utilisateur accède à la page B à partir de la page A et les données du modèle A sont nettoyées et restaurées à leur état initial.

Comportement réel : (effet réel)

Lorsque l'utilisateur accède à la page B à partir de la page A, si une demande de réseau asynchrone se produit dans la page A et accède à la page B avant que la demande de réseau ne soit terminée, la page A efface d'abord les données du modèle A via le réducteur d'effacement, mais lorsque le réseau est en vigueur Une fois la demande terminée, les données renvoyées seront réécrites dans le modèle A, ce qui entraînera des données modifiées de l'activité précédente dans le modèle A.

Puis-je annuler les effets spécifiés lorsque je quitte la page A, ou annuler tous les effets dans un espace de noms ? (Similaire à annuler dans redux-saga)

Versions des packages utilisés : (quelle version de quelle bibliothèque a le problème)

v2.2.3

Commentaire le plus utile

@wss1942 Merci !
De cette façon, les effets peuvent être annulés. Il est plus difficile d'afficher l'état de chargement. Vous ne pouvez pas utiliser directement le fichier loading.effects fourni avec dva pour afficher l'état de chargement. Vous devez écrire votre propre code pour le modifier.
Je l'ai modifié et posté un code de modèle complet

import { getProduct } from '@/services/Products';
import { isRespSucc, showErrorMsg } from '@/utils/utils';

const initState = {};

export default {
  namespace: 'product',

  state: initState,

  effects: {
    /**
      在两个 Effects 之间触发一个竞赛(race)
      如果task先结束,竞赛结束。
      如果task未结束时收到cancel,race effect 将自动取消 task。
    */
    *cancelable({ task, payload }, { call, race, take }) {
      yield race({
        task: call(task, payload),
        cancel: take('cancel'),
      });
    },

    /**
      取消所有未完成的任务,并执行数据清理
    */
    *clear(_, { put }) {
      yield put.resolve({ type: 'cancel' });
      yield put({ type: 'clearState' });
    },

    *getProduct({ payload }, { call, put, cancelled }) {
      // eslint-disable-next-line
      yield put.resolve({ type: 'cancelable', task: getProductCancelable, payload });

      function* getProductCancelable(params) {
        try {
          // 调用网络请求
          const response = yield call(getProduct, params);
          // 返回结果判断
          if (!isRespSucc(response)) {
            showErrorMsg(response);
            return;
          }
          // 取值
          const { productName } = response.data;
          // 调用reducer存值
          yield put({
            type: 'saveState',
            payload: { productName },
          });
        } finally {
          if (yield cancelled()) {
            // TODO: 取消网络请求
          }
        }
      }
    },
    *getCity(_, { call, put, cancelled }) {
      // eslint-disable-next-line
      yield put.resolve({ type: 'cancelable', task: getCityCancelable });

      function* getCityCancelable() {
        // TODO: 具体实现
      }
  },

  reducers: {
    saveState(state, { payload }) {
      const newState = { ...state, ...payload };
      return newState;
    },
    clearState() {
      return initState;
    },
  },
};


Lorsque vous quittez la transaction (componentWillUnmount), dispatch clear peut annuler tous les effets inachevés dans le modèle actuel, et getProductLoading et getCityLoading peuvent également être utilisés normalement.

  componentWillMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'product/getProduct',
      payload: {
        productNumber: '123456',
      },
    });
    dispatch({
      type: 'product/getCity',
    });
  }

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'product/clear',
    });
  }

  function mapStateToProps(state) {
    return {
      getProductLoading: state.loading.effects['product/getProduct'],
      getCityLoading: state.loading.effects['product/getCity'],
    };
  }

Tous les 11 commentaires

Vous pouvez surveiller les changements de routage dans les abonnements du modèle et effacer manuellement l'état du modèle A lorsque la page est une page non-A (quitter la page A) ou une page A (entrer une page).

Merci de répondre. J'ai essayé cette méthode. Il n'y a aucun problème à effacer l'état lui-même. Il peut être effacé soit dans componentWillUnmount, soit lorsque les abonnements surveillent les changements de routage. Le problème est qu'après avoir effacé l'état modal, les données renvoyées par l'opération asynchrone dans les effets Il sera toujours réécrit dans l'état modal, ce qui entraînera des données modifiées de l'activité précédente.
Je surveille même les changements de routage dans les abonnements. Lorsque je quitte la page A, j'utilise unmodel pour désinstaller le modèle A. Il est inutile d'entrer dans la page A et de charger manuellement le modèle A. Si la communication asynchrone revient, tant que le modèle A est trouvé, les données seront écrites, quel que soit ce modèle est le dernier utilisé par l'entreprise ou le modèle nouvellement chargé.
Le meilleur moyen est donc d'effacer l'état dans le modèle et d'annuler tous les effets dans l'espace de noms actuel, mais aucune méthode à appeler n'est trouvée. Demander conseil.

J'ai rencontré le problème de l'affiche d'origine. Je voulais annuler l'effet inachevé lors de la désinstallation du composant, mais j'ai trouvé qu'il n'y avait aucun moyen. Dva ne semble pas fournir ce genre d'api @sorrycc

Est-il possible d'annuler les effets et de séparer les sagas

@dlamon a rencontré la même situation et a voulu nettoyer lorsque l'itinéraire a été changé. Y a-t-il une solution maintenant?

@KyrieChen ne devrait pas être nettoyé pendant le changement de route, car une fois le changement de route terminé, la communication peut ne pas revenir et les données modifiées seront toujours réécrites dans la zone de données de modèle nettoyée après le retour de la communication.

Mon approche actuelle consiste à générer une zone de sous-données avec UUID dans la zone de données du modèle correspondant à la transaction en cours chaque fois que j'entre dans une transaction (lorsque componentWillMount), qui est utilisé pour stocker les données utilisées dans la transaction en cours. UUID est écrit et effacé lorsque la transaction se termine (componentWillUnmount). Similaire à la structure du modèle ci-dessous :
2018-11-07 7 25 31
Si la communication revient après le modèle clair, lors de l'écriture des données, puisque l'UUID utilisé par le réducteur actuel est l'UUID utilisé dans la transaction précédente, les données modifiées seront écrites dans la zone de données de la transaction précédente, et seront ne sera pas généré pour la prochaine transaction.

Mais cette méthode n'est pas bonne non plus : d'une part, elle augmente la complexité logique, et la seconde est que l'obtention de la valeur initiale nécessite d'ajouter un jugement de valeur nul, ce qui augmente la complexité du code. Parce que ce que je fais est principalement pour le système financier, j'ai peur de ce genre de données sales, donc je choisis cette méthode.
Je pense que le meilleur moyen est similaire au mécanisme d'annulation des effets dans redux-saga, mais dva ne semble pas le prendre en charge maintenant.
Si vous avez une meilleure solution, s'il vous plaît @我

@dlamon Je l'ai aussi rencontré. J'ai écrit une démo moi-même et je me sens un peu problématique https://codesandbox.io/s/yqwqpmvwvj

Annuler une méthode de namespace à products à model en effect :
dispatch({ type: 'products/@<strong i="10">@CANCEL_EFFECTS</strong>' });

Est-ce que ce code

        yield sagaEffects.fork(function*() {
          yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`);
          yield sagaEffects.cancel(task);
        });

@wss1942 Merci d'avoir répondu à cette méthode. Je l'ai essayée. Si dispatch({ type:'products/@ @CANCEL_EFFECTS '}) rendra l'effet dans le modal correspondant invalide. Similaire à #796

@dlamon dva ne semble pas fournir d'API pour effacer un effet. Cependant, plusieurs sagas peuvent être écrites dans l'effet défini dans le modèle.

 namespace: 'products',
 effects: {
  *start(){},
  *stop(){},
  watchLogin: [
      function* ({ take, put, call, cancel, fork, cancelled }) {
          yield take('start');
          const timerTask = yield fork(timer)
          const bgSyncTask = yield fork(bgSync)
          yield take('stop')
          yield cancel(bgSyncTask)
          yield cancel(timerTask)

        function* bgSync() {
          try {
            while (true) {
              const result = yield call(delay, 5 * 1000);
              yield put({ type: 'stop' })
            }
          } finally {
            if (yield cancelled())
              yield put({ type: 'log', payload: 'fetch🛑' })
          }
        }
        function* timer(time) {
          let i=0;
          while (true) {
            yield put({ type: 'log', payload: i++ })
            yield delay(1000)
          }
        }
      },
      { type: 'watcher' },
    ],
}

bgSync est votre tâche asynchrone, vous pouvez démarrer la tâche avec action: démarrer et annuler la tâche avec
De plus, si la tâche asynchrone est une requête réseau, une opération peut également être nécessaire pour annuler la requête réseau. Par exemple, axios peut être annulé avec axios.CancelToken.

@wss1942 Merci !
De cette façon, les effets peuvent être annulés. Il est plus difficile d'afficher l'état de chargement. Vous ne pouvez pas utiliser directement le fichier loading.effects fourni avec dva pour afficher l'état de chargement. Vous devez écrire votre propre code pour le modifier.
Je l'ai modifié et posté un code de modèle complet

import { getProduct } from '@/services/Products';
import { isRespSucc, showErrorMsg } from '@/utils/utils';

const initState = {};

export default {
  namespace: 'product',

  state: initState,

  effects: {
    /**
      在两个 Effects 之间触发一个竞赛(race)
      如果task先结束,竞赛结束。
      如果task未结束时收到cancel,race effect 将自动取消 task。
    */
    *cancelable({ task, payload }, { call, race, take }) {
      yield race({
        task: call(task, payload),
        cancel: take('cancel'),
      });
    },

    /**
      取消所有未完成的任务,并执行数据清理
    */
    *clear(_, { put }) {
      yield put.resolve({ type: 'cancel' });
      yield put({ type: 'clearState' });
    },

    *getProduct({ payload }, { call, put, cancelled }) {
      // eslint-disable-next-line
      yield put.resolve({ type: 'cancelable', task: getProductCancelable, payload });

      function* getProductCancelable(params) {
        try {
          // 调用网络请求
          const response = yield call(getProduct, params);
          // 返回结果判断
          if (!isRespSucc(response)) {
            showErrorMsg(response);
            return;
          }
          // 取值
          const { productName } = response.data;
          // 调用reducer存值
          yield put({
            type: 'saveState',
            payload: { productName },
          });
        } finally {
          if (yield cancelled()) {
            // TODO: 取消网络请求
          }
        }
      }
    },
    *getCity(_, { call, put, cancelled }) {
      // eslint-disable-next-line
      yield put.resolve({ type: 'cancelable', task: getCityCancelable });

      function* getCityCancelable() {
        // TODO: 具体实现
      }
  },

  reducers: {
    saveState(state, { payload }) {
      const newState = { ...state, ...payload };
      return newState;
    },
    clearState() {
      return initState;
    },
  },
};


Lorsque vous quittez la transaction (componentWillUnmount), dispatch clear peut annuler tous les effets inachevés dans le modèle actuel, et getProductLoading et getCityLoading peuvent également être utilisés normalement.

  componentWillMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'product/getProduct',
      payload: {
        productNumber: '123456',
      },
    });
    dispatch({
      type: 'product/getCity',
    });
  }

  componentWillUnmount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'product/clear',
    });
  }

  function mapStateToProps(state) {
    return {
      getProductLoading: state.loading.effects['product/getProduct'],
      getCityLoading: state.loading.effects['product/getCity'],
    };
  }
Cette page vous a été utile?
0 / 5 - 0 notes