使用 redux-observable 显示确认对话框
show confirmation dialog using redux-observable
我正在尝试使用确认对话框实现删除史诗。
我想到了这个方法。它的优点是易于测试。
我的问题是,这是一个好方法吗,我应该担心添加 takeUntil(action$.ofType(MODAL_NO_CLICKED))
吗?
如果您能想到更好的实现方法,请告诉我。
const deleteNotification$ = (id, { ajax }) => ajax({ url: `api/delete/{id}` });
// showYesNo is an action to eventually show a dialog using this approach
const showYesNo = payload => ({
type: SHOW_MODAL,
modalType: MODAL_TYPE_YES_NO,
modalProps: { ...payload },
});
const deleteNotificationEpic = (action$, store, dependencies) => {
let uid = dependencies.uid; // dependencies.uid is added here to allow passing the uid during unit test.
return merge(
// Show confirmation dialog.
action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
map(action => {
uid = shortid.generate();
return showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
}),
),
// Deletes the notification if the user clicks on Yes
action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
mergeMap(() => of(deleteNotificationSuccess())),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
),
);
};
我知道我可以在 React 级别显示确认对话框,并且仅在用户单击“是”时分派删除操作,但我的问题是更一般的情况,我可能有一些逻辑(调用 back-end) 在决定是否显示确认对话框之前。
由于评论的长度有限,我发布了一个答案,尽管它还没有一个。
我认为我无法为您提供指导,因为示例代码缺少实现,因此不清楚到底发生了什么。特别是 showYesNo 和 deleteNotification$ 是什么?
顺便说一句,您生成的唯一 ID 只会在史诗启动时生成一次。这似乎是一个错误,因为唯一 ID 通常不可重复使用?
const deleteNotificationEpic = (action$, store, dependencies) => {
const uid = shortid.generate();
你的解决方案总体上是好的。可能会出现奇怪的错误,因为即使未显示通知,也会始终监听 MODAL_YES_CLICKED
,尽管这是否重要还有待商榷。
当我需要类似的模式时,我只根据需要亲自设置侦听器,并确保有一些方法可以取消(比如 MODAL_NO_CLICKED
),这样我就不会泄漏内存。像这样按顺序排列有助于我理解预期的流程。
return action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
switchMap(action => {
uid = shortid.generate();
return action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
take(1),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
map(() => deleteNotificationSuccess()),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
takeUntil(action$.pipe(ofType(MODAL_NO_CLICKED))),
startWith(
showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
)
)
}),
)
关于我的方法与你的方法的一个有趣的事情是我的方法有点冗长,因为我需要 takeUntil
以及 take(1)
(所以我们不会泄漏内存)。
单元测试:
it('should delete the notification when MODAL_YES_CLICKED is dispatched', () => {
const uid = 1234;
shortid.generate.mockImplementation(() => uid);
const store = null;
const dependencies = {
ajax: () => of({}),
uid,
};
const inputValues = {
a: action.deleteNotificationRequest(12345, uid),
b: buttonYesClicked({ id: 12345, uid }),
};
const expectedValues = {
a: showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
id: 12345,
uid,
},
}),
b: showToastSuccessDeleted(),
c: action.loadNotificationsRequest(false),
};
const inputMarble = ' a---b';
const expectedMarble = '---a---(bc)';
const ts = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const action$ = new ActionsObservable(ts.createHotObservable(inputMarble, inputValues));
const outputAction = epic.deleteNotificationEpic(action$, store, dependencies);
ts.expectObservable(outputAction).toBe(expectedMarble, expectedValues);
ts.flush();
});
我正在尝试使用确认对话框实现删除史诗。
我想到了这个方法。它的优点是易于测试。
我的问题是,这是一个好方法吗,我应该担心添加 takeUntil(action$.ofType(MODAL_NO_CLICKED))
吗?
如果您能想到更好的实现方法,请告诉我。
const deleteNotification$ = (id, { ajax }) => ajax({ url: `api/delete/{id}` });
// showYesNo is an action to eventually show a dialog using this approach
const showYesNo = payload => ({
type: SHOW_MODAL,
modalType: MODAL_TYPE_YES_NO,
modalProps: { ...payload },
});
const deleteNotificationEpic = (action$, store, dependencies) => {
let uid = dependencies.uid; // dependencies.uid is added here to allow passing the uid during unit test.
return merge(
// Show confirmation dialog.
action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
map(action => {
uid = shortid.generate();
return showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
}),
),
// Deletes the notification if the user clicks on Yes
action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
mergeMap(() => of(deleteNotificationSuccess())),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
),
);
};
我知道我可以在 React 级别显示确认对话框,并且仅在用户单击“是”时分派删除操作,但我的问题是更一般的情况,我可能有一些逻辑(调用 back-end) 在决定是否显示确认对话框之前。
由于评论的长度有限,我发布了一个答案,尽管它还没有一个。
我认为我无法为您提供指导,因为示例代码缺少实现,因此不清楚到底发生了什么。特别是 showYesNo 和 deleteNotification$ 是什么?
顺便说一句,您生成的唯一 ID 只会在史诗启动时生成一次。这似乎是一个错误,因为唯一 ID 通常不可重复使用?
const deleteNotificationEpic = (action$, store, dependencies) => {
const uid = shortid.generate();
你的解决方案总体上是好的。可能会出现奇怪的错误,因为即使未显示通知,也会始终监听 MODAL_YES_CLICKED
,尽管这是否重要还有待商榷。
当我需要类似的模式时,我只根据需要亲自设置侦听器,并确保有一些方法可以取消(比如 MODAL_NO_CLICKED
),这样我就不会泄漏内存。像这样按顺序排列有助于我理解预期的流程。
return action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
switchMap(action => {
uid = shortid.generate();
return action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
take(1),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
map(() => deleteNotificationSuccess()),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
takeUntil(action$.pipe(ofType(MODAL_NO_CLICKED))),
startWith(
showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
)
)
}),
)
关于我的方法与你的方法的一个有趣的事情是我的方法有点冗长,因为我需要 takeUntil
以及 take(1)
(所以我们不会泄漏内存)。
单元测试:
it('should delete the notification when MODAL_YES_CLICKED is dispatched', () => {
const uid = 1234;
shortid.generate.mockImplementation(() => uid);
const store = null;
const dependencies = {
ajax: () => of({}),
uid,
};
const inputValues = {
a: action.deleteNotificationRequest(12345, uid),
b: buttonYesClicked({ id: 12345, uid }),
};
const expectedValues = {
a: showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
id: 12345,
uid,
},
}),
b: showToastSuccessDeleted(),
c: action.loadNotificationsRequest(false),
};
const inputMarble = ' a---b';
const expectedMarble = '---a---(bc)';
const ts = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const action$ = new ActionsObservable(ts.createHotObservable(inputMarble, inputValues));
const outputAction = epic.deleteNotificationEpic(action$, store, dependencies);
ts.expectObservable(outputAction).toBe(expectedMarble, expectedValues);
ts.flush();
});