场景:如果项目中存在两个页面A和B,分别对应model A和model B,在每个页面中都存在一些异步的网络请求, 在effects中发起并将返回数据通过reducer写入到当前model的state中。
在离开页面A或页面B时,我希望清空对应model中的数据,以免成为下次重新使用时的脏数据。
我在model 里编写了 clear 的 reducer,在componentWillUnmount方法中
dispatch({ type:${model.namespace}/clear})
用户从A页面进入B页面,model A中的数据被清理,恢复到初始状态。
用户从A页面进入B页面时,如果A页面中发生了异步网络请求,在网络请求还没有完成时进入B页面,A页面会首先通过clear的reducer清除掉model A数据,但是当effects中的网络请求完成时,会将返回数据重新写入model A,导致model A中存在上一次业务的脏数据。
请问是否能够在离开A页面时cancel指定的effects,或者cancel掉一个namespace中所有的effects。(类似redux-saga中的cancel)
v2.2.3
你可以在 model 的 subscriptions 监听路由变化,当页面为非A页面(离开A页面)或A页面(进入A页面)时手动清除 model A 的state 状态。
谢谢回答。这个方法我已经试过了,清除state状态本身没有什么问题,无论是在componentWillUnmount时清除还是在subscriptions监听路由变化时清除都可以,问题是清除了modal 的state后,effects中的异步操作返回的数据还是会重新写入modal的state中,导致上一次业务的脏数据。
我什至在subscriptions监听路由变化,离开A页面时使用unmodel卸载掉model A,进入A页面再手动加载model A都没用,如果异步通信回来时,只要发现存在model A,都会把数据写进入,不管这个model是上一次业务使用的model还是新加载的model。
所以最好的办法是清除model中state的同时cancel掉当前namespace中所有的effects,但是没有找到调用的方法。求指点。
楼主的问题,我也遇到过,想在组件卸载时取消掉未完成的effect,但是发现没有办法,dva好像没有提供这类api啊@sorrycc
是否可以取消effects,将sagas独立出来
@dlamon 遇到的同样的情况,想在路由切换的时候清理。请问现在有解决方法吗?
@KyrieChen 在路由切换时清理估计也不行,因为路由切换完成后,通讯有可能还没回来,通讯回来后仍然会把脏数据写回到清理后的model数据区。
我现在的做法是每次进入交易时(componentWillMount时),在当前交易对应的model数据区中生成一个带UUID的子数据区,用于存储当次交易使用的数据,在reducer写入数据时带UUID写入,在交易退出(componentWillUnmount)时清理(clear)。类似于下面的model结构:
如果通讯在clear model后才回来,写入数据时由于当前reducer使用的UUID是上一次交易使用的UUID,则会把脏数据写入到上一个交易的数据区中,不会对下次交易产生影响。
但是这种方式也不算好,第一增加了逻辑复杂性,第二是获取初始值需要增加空值判断,增加了代码复杂性。由于我做的主要是针对金融系统,很怕这种脏数据,才选择这种方式。
我觉得最好的方式还是类似于redux-saga中cancel effects机制,但是dva现在好像没有支持。
如果你有更好的解决办法,请@我
@dlamon 我也碰到了,自己写了个demo,感觉有点问题 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 感谢回复
这个方法我试过,如果dispatch({ type: 'products/@@CANCEL_EFFECTS' })会导致对应modal里面的effect无效。类似于#796
@dlamon dva貌似没有提供清除某个effect的api。但是model中定义的effect中可以写多个saga。
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开始任务,action:stop取消任务。这能实现cancel某个effect。
另外,异步任务如果是网络请求,可能还需要一个取消网络请求的操作,比如axios可以用axios.CancelToken取消。
@wss1942 感谢!
这种方式可以实现取消effects,在显示loading状态的时候要麻烦一些,不能直接使用dva自带的loading.effects来显示loading状态,需要自己手工编写代码来变更。
我修改了下,贴一个完整的model代码
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),dispatch clear就可以取消当前model中所有未完成的effects,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'],
};
}
最有用的评论
@wss1942 感谢!
这种方式可以实现取消effects,在显示loading状态的时候要麻烦一些,不能直接使用dva自带的loading.effects来显示loading状态,需要自己手工编写代码来变更。
我修改了下,贴一个完整的model代码
在离开交易时(componentWillUnmount),dispatch clear就可以取消当前model中所有未完成的effects,getProductLoading和getCityLoading也可以正常使用。