Dva: Cómo cancelar efectos

Creado en 8 jun. 2018  ·  11Comentarios  ·  Fuente: dvajs/dva

Código para reproducir el problema: (proporcione un código o pasos reproducibles)

Escenario: Si hay dos páginas A y B en el proyecto, correspondientes al modelo A y al modelo B, hay algunas solicitudes de red asíncronas en cada página, que se inician en efectos y los datos devueltos se escriben en el modelo actual a través del reductor. estado.

Cuando salgo de la página A o la página B, quiero borrar los datos en el modelo correspondiente para que no se conviertan en datos sucios cuando los reutilice la próxima vez.
Escribí un reductor claro en el modelo, en el método componentWillUnmount
despacho ({tipo: $ {model.namespace} / clear})

Comportamiento esperado: (efecto normal esperado)

El usuario ingresa a la página B desde la página A, y los datos del modelo A se limpian y se restauran al estado inicial.

Comportamiento real: (efecto real)

Cuando el usuario ingresa a la página B desde la página A, si se produce una solicitud de red asíncrona en la página A e ingresa a la página B antes de que se complete la solicitud de red, la página A primero borrará los datos del modelo A a través del reductor transparente, pero cuando la red entre en vigor Cuando se completa la solicitud, los datos devueltos se reescribirán en el modelo A, lo que dará como resultado datos sucios del negocio anterior en el modelo A.

¿Puedo cancelar los efectos especificados cuando salgo de la página A, o cancelar todos los efectos en un espacio de nombres? (Similar a cancelar en redux-saga)

Versiones de paquetes utilizados: (qué versión de qué biblioteca tiene el problema)

v2.2.3

Comentario más útil

@ wss1942 ¡Gracias!
De esta manera, los efectos se pueden cancelar. Es más problemático mostrar el estado de carga. No puede usar directamente loading.effects que viene con dva para mostrar el estado de carga. Debe escribir su propio código para cambiarlo.
Lo modifiqué y publiqué un código de modelo completo

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


Al dejar la transacción (componentWillUnmount), dispatch clear puede cancelar todos los efectos sin terminar en el modelo actual, y getProductLoading y getCityLoading también se pueden usar normalmente.

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

Todos 11 comentarios

Puede supervisar los cambios de enrutamiento en las suscripciones del modelo y borrar manualmente el estado del modelo A cuando la página no es una página A (salir de la página A) o una página A (entrar en la página A).

Gracias por responder. Probé este método. No hay ningún problema para borrar el estado en sí. Se puede borrar en componentWillUnmount o cuando las suscripciones monitorean los cambios de enrutamiento. El problema es que después de borrar el estado modal, los datos devueltos por la operación asincrónica en los efectos Todavía se reescribirá en el estado modal, lo que dará como resultado datos sucios del negocio anterior.
Incluso superviso los cambios de enrutamiento en las suscripciones. Cuando salgo de la página A, uso unmodel para desinstalar el modelo A. Es inútil ingresar a la página A y cargar manualmente el modelo A. Si la comunicación asíncrona regresa, siempre que se encuentre el modelo A, los datos se escribirán, independientemente de si este modelo es el último utilizado por la empresa o el modelo recién cargado.
Entonces, la mejor manera es borrar el estado en el modelo y cancelar todos los efectos en el espacio de nombres actual, pero no se encuentra ningún método para llamar. Busca consejo.

Me encontré con el problema del póster original. Quería cancelar el efecto inacabado cuando se desinstaló el componente, pero descubrí que no había forma. Dva no parece proporcionar este tipo de api @sorrycc

¿Es posible cancelar los efectos y separar las sagas?

@dlamon se encontró con la misma situación y quiso limpiar cuando se cambió la ruta. ¿Existe una solución ahora?

No se espera que

Mi enfoque actual es generar un área de subdatos con UUID en el área de datos del modelo correspondiente a la transacción actual cada vez que ingreso una transacción (cuando componentWillMount), que se usa para almacenar los datos usados ​​en la transacción actual. UUID está escrito y se borra cuando finaliza la transacción (componentWillUnmount). Similar a la estructura del modelo a continuación:
2018-11-07 7 25 31
Si la comunicación vuelve después del modelo claro, al escribir datos, dado que el UUID utilizado por el reductor actual es el UUID utilizado en la transacción anterior, los datos sucios se escribirán en el área de datos de la transacción anterior y no se generará para la siguiente transacción.

Pero este método tampoco es bueno, primero aumenta la complejidad lógica, y segundo, que para obtener el valor inicial es necesario agregar un juicio de valor nulo, lo que aumenta la complejidad del código. Porque lo que hago es principalmente para el sistema financiero, le tengo miedo a este tipo de datos sucios, así que elijo este método.
Creo que la mejor manera es similar al mecanismo de cancelación de efectos en redux-saga, pero dva no parece admitirlo ahora.
Si tiene una mejor solución, por favor @ 我

@dlamon También me encontré con él. Escribí una demostración yo mismo, y me siento un poco problemático https://codesandbox.io/s/yqwqpmvwvj

Cancelar un namespace a products a model en effect método:
dispatch({ type: 'products/@<strong i="10">@CANCEL_EFFECTS</strong>' });

Es este codigo

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

@ wss1942 Gracias por responder a este método. Lo probé. Si el envío ({tipo: 'productos / @ @CANCEL_EFFECTS '}) hará que el efecto en el modal correspondiente no sea válido. Similar al # 796

@dlamon dva no parece proporcionar una API para borrar un efecto. Sin embargo, se pueden escribir múltiples sagas en el efecto definido en el modelo.

 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 es su tarea asincrónica, puede iniciar la tarea con la acción: iniciar y cancelar la tarea con la
Además, si la tarea asíncrona es una solicitud de red, es posible que también necesite una operación para cancelar la solicitud de red. Por ejemplo, axios se puede cancelar con axios.CancelToken.

@ wss1942 ¡Gracias!
De esta manera, los efectos se pueden cancelar. Es más problemático mostrar el estado de carga. No puede usar directamente loading.effects que viene con dva para mostrar el estado de carga. Debe escribir su propio código para cambiarlo.
Lo modifiqué y publiqué un código de modelo completo

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


Al dejar la transacción (componentWillUnmount), dispatch clear puede cancelar todos los efectos sin terminar en el modelo actual, y getProductLoading y getCityLoading también se pueden usar normalmente.

  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'],
    };
  }
¿Fue útil esta página
0 / 5 - 0 calificaciones