Dva: Cara membatalkan efek

Dibuat pada 8 Jun 2018  ·  11Komentar  ·  Sumber: dvajs/dva

Kode untuk mereproduksi masalah: (Harap berikan kode atau langkah yang dapat direproduksi)

Skenario: Jika ada dua halaman A dan B dalam proyek, sesuai dengan model A dan model B, ada beberapa permintaan jaringan asinkron di setiap halaman, yang dimulai dalam efek dan data yang dikembalikan ditulis ke model saat ini melalui peredam negara.

Ketika saya meninggalkan halaman A atau halaman B, saya ingin menghapus data dalam model yang sesuai agar tidak menjadi data kotor ketika saya menggunakannya kembali di lain waktu.
Saya menulis peredam yang jelas dalam model, dalam metode componentWillUnmount
pengiriman({ type:${model.namespace}/clear})

Perilaku yang diharapkan: (efek normal yang diharapkan)

Pengguna memasuki halaman B dari halaman A, dan data dalam model A dibersihkan dan dikembalikan ke keadaan awal.

Perilaku aktual: (efek aktual)

Ketika pengguna memasuki halaman B dari halaman A, jika permintaan jaringan asinkron terjadi di halaman A dan memasuki halaman B sebelum permintaan jaringan selesai, halaman A pertama-tama akan menghapus data model A melalui peredam yang jelas, tetapi ketika jaringan dalam efek Ketika permintaan selesai, data yang dikembalikan akan ditulis ulang ke model A, menghasilkan data kotor dari bisnis sebelumnya di model A.

Bisakah saya membatalkan efek yang ditentukan ketika saya meninggalkan halaman A, atau membatalkan semua efek di namespace? (Mirip dengan membatalkan di redux-saga)

Versi paket yang digunakan: (versi perpustakaan mana yang bermasalah)

v2.2.3

Komentar yang paling membantu

@wss1942 Terima kasih!
Dengan cara ini, efek dapat dibatalkan. Lebih merepotkan untuk menampilkan status pemuatan. Anda tidak dapat langsung menggunakan pemuatan. efek yang disertakan dengan dva untuk menampilkan status pemuatan. Anda perlu menulis kode Anda sendiri untuk mengubahnya.
Saya memodifikasinya dan memposting kode model lengkap

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


Saat meninggalkan transaksi (componentWillUnmount), pengiriman clear dapat membatalkan semua efek yang belum selesai dalam model saat ini, dan getProductLoading dan getCityLoading juga dapat digunakan secara normal.

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

Semua 11 komentar

Anda dapat memantau perubahan perutean dalam langganan model, dan menghapus status model A secara manual saat halaman tersebut adalah halaman non-A (keluar dari halaman A) atau halaman A (masuk ke halaman A).

Terimakasih telah menjawab. Saya telah mencoba metode ini. Tidak ada masalah dalam menghapus status itu sendiri. Ini dapat dihapus baik di componentWillUnmount atau ketika langganan memantau perubahan perutean. Masalahnya adalah setelah menghapus status modal, data dikembalikan oleh operasi asinkron di efek Itu masih akan ditulis ulang ke status modal, menghasilkan data kotor dari bisnis sebelumnya.
Saya bahkan memantau perubahan perutean dalam langganan. Ketika saya meninggalkan halaman A, saya menggunakan unmodel untuk menghapus model A. Tidak ada gunanya memasuki halaman A dan memuat model A secara manual. Jika komunikasi asinkron kembali, selama model A ditemukan, data akan ditulis, terlepas dari Apakah model ini yang terakhir digunakan oleh bisnis atau model yang baru dimuat.
Jadi cara terbaik adalah menghapus status dalam model dan membatalkan semua efek di namespace saat ini, tetapi tidak ada metode untuk memanggil yang ditemukan. Carilah saran.

Saya mengalami masalah dengan poster aslinya. Saya ingin membatalkan efek yang belum selesai ketika komponen dicopot, tetapi saya menemukan bahwa tidak ada cara. Dva sepertinya tidak menyediakan api semacam ini @sorrycc

Apakah mungkin untuk membatalkan efek dan memisahkan saga

@dlamon mengalami situasi yang sama dan ingin membersihkan ketika rute dialihkan. Apakah ada solusi sekarang?

@KyrieChen tidak diharapkan untuk dibersihkan selama pengalihan rute, karena setelah pengalihan rute selesai, komunikasi mungkin tidak kembali, dan data kotor masih akan ditulis kembali ke area data model yang dibersihkan setelah komunikasi kembali.

Pendekatan saya saat ini adalah menghasilkan area sub-data dengan UUID di area data model yang sesuai dengan transaksi saat ini setiap kali saya memasukkan transaksi (ketika componentWillMount), yang digunakan untuk menyimpan data yang digunakan dalam transaksi saat ini. dan dihapus saat transaksi keluar (componentWillUnmount). Mirip dengan struktur model di bawah ini:
2018-11-07 7 25 31
Jika komunikasi kembali setelah model yang jelas, saat menulis data, karena UUID yang digunakan oleh peredam saat ini adalah UUID yang digunakan pada transaksi sebelumnya, data kotor akan ditulis ke area data transaksi sebelumnya, dan akan tidak dihasilkan untuk transaksi berikutnya.

Tetapi metode ini juga tidak baik, pertama, meningkatkan kompleksitas logika, dan kedua adalah bahwa memperoleh nilai awal memerlukan penambahan penilaian nilai nol, yang meningkatkan kompleksitas kode. Karena apa yang saya lakukan terutama untuk sistem keuangan, saya takut data kotor semacam ini, jadi saya memilih metode ini.
Saya pikir cara terbaik mirip dengan mekanisme pembatalan efek di redux-saga, tetapi dva tampaknya tidak mendukungnya sekarang.
Jika Anda memiliki solusi yang lebih baik, silakan @我

@dlamon saya juga mengalaminya. Saya menulis demo sendiri, dan saya merasa sedikit bermasalah https://codesandbox.io/s/yqwqpmvwvj

Batalkan namespace ke products ke model dalam metode effect :
dispatch({ type: 'products/@<strong i="10">@CANCEL_EFFECTS</strong>' });

Apakah kode ini?

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

@wss1942 Terima kasih telah membalas metode ini. Saya mencobanya. Jika dispatch({ type:'products/@ @CANCEL_EFFECTS '}) akan menyebabkan efek pada modal yang sesuai menjadi tidak valid. Mirip dengan #796

@dlamon dva tampaknya tidak memberikan api untuk menghapus efek. Namun, beberapa kisah dapat ditulis dalam efek yang ditentukan dalam model.

 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 adalah tugas asinkron Anda, Anda dapat memulai tugas dengan action: start dan membatalkan tugas dengan
Selain itu, jika tugas asinkron adalah permintaan jaringan, mungkin juga memerlukan operasi untuk membatalkan permintaan jaringan. Misalnya, axios dapat dibatalkan dengan axios.CancelToken.

@wss1942 Terima kasih!
Dengan cara ini, efek dapat dibatalkan. Lebih merepotkan untuk menampilkan status pemuatan. Anda tidak dapat langsung menggunakan pemuatan. efek yang disertakan dengan dva untuk menampilkan status pemuatan. Anda perlu menulis kode Anda sendiri untuk mengubahnya.
Saya memodifikasinya dan memposting kode model lengkap

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


Saat meninggalkan transaksi (componentWillUnmount), pengiriman clear dapat membatalkan semua efek yang belum selesai dalam model saat ini, dan getProductLoading dan getCityLoading juga dapat digunakan secara normal.

  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'],
    };
  }
Apakah halaman ini membantu?
0 / 5 - 0 peringkat