Dva: Как отменить эффекты

Созданный на 8 июн. 2018  ·  11Комментарии  ·  Источник: dvajs/dva

Код для воспроизведения проблемы: (укажите воспроизводимый код или шаги)

Сценарий: если в проекте есть две страницы A и B, соответствующие модели A и модели B, на каждой странице есть несколько асинхронных сетевых запросов, которые инициируются в эффектах, а возвращенные данные записываются в текущую модель через редуктор. штат.

Когда я покидаю страницу A или страницу B, я хочу очистить данные в соответствующей модели, чтобы не стать грязными при повторном использовании в следующий раз.
Я написал четкий редуктор в модели, в методе componentWillUnmount
отправка ({type: $ {model.namespace} / clear})

Ожидаемое поведение: (ожидаемый нормальный эффект)

Пользователь переходит на страницу B со страницы A, и данные в модели A очищаются и восстанавливаются до исходного состояния.

Фактическое поведение: (фактический эффект)

Когда пользователь переходит на страницу B со страницы A, если асинхронный сетевой запрос возникает на странице A и переходит на страницу B до того, как сетевой запрос будет завершен, страница A сначала очистит данные модели A с помощью редуктора очистки, но когда сеть действует Когда запрос будет завершен, возвращенные данные будут перезаписаны в модель A, в результате чего получатся грязные данные из предыдущего бизнеса в модели A.

Могу ли я отменить указанные эффекты, когда покидаю страницу A, или отменить все эффекты в пространстве имен? (Аналогично отменить в redux-saga)

Версии используемых пакетов: (в какой версии какой библиотеки возникла проблема)

v2.2.3

Самый полезный комментарий

@ wss1942 Спасибо!
Таким образом, эффекты можно отменить. Отображать статус загрузки сложнее. Вы не можете напрямую использовать loading.effects, поставляемые с dva, для отображения статуса загрузки. Чтобы изменить его, вам нужно написать свой собственный код.
Я изменил его и выложил полный код модели

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;
    },
  },
};


При выходе из транзакции (componentWillUnmount) диспетчерская очистка может отменить все незавершенные эффекты в текущей модели, а getProductLoading и getCityLoading также могут использоваться в обычном режиме.

  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'],
    };
  }

Все 11 Комментарий

Вы можете отслеживать изменения маршрутизации в подписках модели и вручную очищать состояние модели A, если страница не является страницей A (оставьте страницу A) или страницей A (введите страницу A).

Спасибо за ответы. Я пробовал этот метод. Нет проблем с очисткой самого состояния. Его можно очистить либо в componentWillUnmount, либо когда подписки отслеживают изменения маршрутизации. Проблема в том, что после очистки модального состояния данные, возвращаемые асинхронной операцией в эффектах Он все равно будет переписан в модальное состояние, что приведет к появлению грязных данных из предыдущего бизнеса.
Я даже отслеживаю изменения маршрутизации в подписках. Когда я покидаю страницу A, я использую unmodel для удаления модели A. Бесполезно вводить страницу A и вручную загружать модель A. Если асинхронная связь возвращается, пока модель A найдена, данные будут записаны независимо от того, является ли эта модель последней, использовавшейся в бизнесе, или новой загруженной моделью.
Итак, лучший способ - очистить состояние в модели и отменить все эффекты в текущем пространстве имен, но метод для вызова не найден. Искать совета.

Я столкнулся с проблемой исходного плаката. Я хотел отменить незавершенный эффект, когда компонент был удален, но обнаружил, что это невозможно. Dva, похоже, не предоставляет такого рода api @sorrycc

Можно ли отменить эффекты и разделить саги

@dlamon столкнулся с той же ситуацией и хотел очистить при переключении маршрута. Есть ли решение сейчас?

Не ожидается, что @KyrieChen очистит во время переключения маршрута, потому что после завершения переключения маршрута обмен данными может не возобновиться, а грязные данные все равно будут записаны обратно в очищенную область данных модели после возобновления связи.

Мой текущий подход заключается в создании области под-данных с UUID в области данных модели, соответствующей текущей транзакции, каждый раз, когда я вхожу в транзакцию (когда componentWillMount), которая используется для хранения данных, используемых в текущей транзакции. UUID записывается и сбрасывается при выходе из транзакции (componentWillUnmount). Подобно структуре модели ниже:
2018-11-07 7 25 31
Если связь возвращается после модели очистки при записи данных, поскольку UUID, используемый текущим редуктором, является UUID, используемым в предыдущей транзакции, грязные данные будут записаны в область данных предыдущей транзакции и будут не будет сгенерирован для следующей транзакции.

Но и этот метод не годится: во-первых, он увеличивает логическую сложность, а во-вторых, получение начального значения требует добавления суждения о нулевом значении, что увеличивает сложность кода. Поскольку то, что я делаю, в основном предназначено для финансовой системы, я боюсь такого рода грязных данных, поэтому выбираю этот метод.
Я думаю, что лучший способ похож на механизм отмены эффектов в redux-saga, но сейчас dva, похоже, не поддерживает его.
Если у вас есть лучшее решение, @ 我

@dlamon Я тоже столкнулся с этим. Я сам написал демо, и мне кажется, что это немного проблематично https://codesandbox.io/s/yqwqpmvwvj

Отмените namespace на products на model в методе effect :
dispatch({ type: 'products/@<strong i="10">@CANCEL_EFFECTS</strong>' });

Это код

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

@ wss1942 Спасибо за ответ на этот метод. Я попробовал. Если отправка ({type: 'products / @ @CANCEL_EFFECTS '}) приведет к тому, что эффект в соответствующем модальном окне будет недействительным. Похоже на: # 796

@dlamon dva , похоже, не предоставляет API для очистки эффекта. Однако в эффекте, определенном в модели, можно записать несколько саг.

 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 - это ваша асинхронная задача, вы можете запустить задачу с помощью action: start и отменить задачу с помощью
Кроме того, если асинхронная задача является сетевым запросом, может также потребоваться операция для отмены сетевого запроса.Например, axios можно отменить с помощью axios.CancelToken.

@ wss1942 Спасибо!
Таким образом, эффекты можно отменить. Отображать статус загрузки сложнее. Вы не можете напрямую использовать loading.effects, поставляемые с dva, для отображения статуса загрузки. Чтобы изменить его, вам нужно написать свой собственный код.
Я изменил его и выложил полный код модели

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;
    },
  },
};


При выходе из транзакции (componentWillUnmount) диспетчерская очистка может отменить все незавершенные эффекты в текущей модели, а getProductLoading и getCityLoading также могут использоваться в обычном режиме.

  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'],
    };
  }
Была ли эта страница полезной?
0 / 5 - 0 рейтинги