0

我正在尝试使用确认对话框来实现删除史诗。

我想出了这种方法。它的优点是易于测试。

我的问题是,这是一个好方法,我应该担心添加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 https://stackoverflow.com/a/35641680/235659
    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 级别显示确认对话框,并且仅在用户单击“是”时才调度删除操作,但我的问题是一个更普遍的情况,在决定显示之前我可能有一些逻辑(调用后端)确认对话框与否。

4

2 回答 2

2

您的解决方案通常很好。有可能出现奇怪的错误,因为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();
    });
于 2018-05-16T20:30:18.923 回答
0

由于评论的长度受到限制,即使还没有答案,我也会发布答案。

我认为我无法为您提供指导,因为示例代码缺少实现,因此不清楚究竟发生了什么。特别是 showYesNo 和 deleteNotification$ 是什么?


顺便说一句,您生成的唯一 ID 仅在史诗启动时完成一次。这似乎是一个错误,因为唯一 ID 通常不可重复使用?

const deleteNotificationEpic = (action$, store, dependencies) => {
  const uid = shortid.generate();
于 2018-05-15T22:35:21.750 回答