我有一个场景,用户填写一份表单,选择 3 个所需文件,然后在提交表单时,我想使用 NgRx 链接一次上传一个文件的操作,使用从服务器返回的 ID 值,最后使用返回值创建一个对象。
我还需要处理文件上传中的任何错误,通知用户并让他们,可能选择另一个文件并继续该过程。
我正在尝试学习链接操作的最佳实践和方法,但遇到了一些问题。
我有 3 个类似的文件上传操作
add_first_file
、add_second_file
、add_third_file
:
export const add_first_file = createAction(
'[VIEW - CREATE OBJECT] - Add The First File',
props<{
file: any;
}>()
);
export const add_first_file_success = createAction(
'[VIEW - CREATE OBJECT] - Add The First File Success',
props<{ created_file_id: string }>()
);
export const add_first_file_failure = createAction(
'[VIEW - CREATE OBJECT] - Add The First File Failure',
props<{ error: string }>()
);
我的第一次尝试是在第一个效果中触发多个操作,但显然这不是一个有效的方法,正如here
所提到的然后我尝试创建返回下一个操作文件上传的效果,以便在我的 NgRx 效果中:
add_first_file = createEffect(() => {
return this.actions$.pipe(
ofType(actions.add_first_file),
switchMap((_file) =>
from(
this.http_service.upload_a_file(
_file.file
)
).pipe(
map((_id) => {
return actions.add_second_file({
created_file_id: _id,
});
}),
catchError((error: any) => {
return of(
actions.add_first_file_failure({
error: error,
})
);
})
)
)
);
});
然后我将返回的值分配给我商店中的对象:
on(actions.add_first_file_success, (state, { created_file_id }) => {
return {
...state,
created_file_id: created_file_id,
status: 'success',
}
}),
on(actions.add_first_file_failure, (state, { error }) => {
return {
...state,
status: 'failed',
}
}),
这导致了无限循环,并且还产生了其他问题,例如文件上传错误,链将停止,并且该方法还需要重新上传已经成功上传的文件才能使用链式成功后返回操作。
我还尝试了一个返回一系列操作的操作,正如我在用例中发现的那样,用于触发多个操作,尽管这不是最佳实践:
add_files = createEffect(() => {
return this.actions$.pipe(
ofType(actions.form_submit),
switchMap((_form) => [
actions.add_first_file(_form.first_file),
actions.add_second_file(_form.second_file),
actions.add_third_file(_form.third_file)
]
)
);
});
这也不允许我处理我认为的个别可能的错误。
我希望有人可以帮助我提供一些很好的示例,说明如何最好地使用 NgRx 来链接操作或其他方法,以解决我描述的用例。
您想要链接效果 - 即使用 3 个效果,每个文件一个,其中第二个效果由第一个效果后续触发,第三个效果由第二个效果后续触发:
add_first_file = createEffect(() => this.actions$.pipe(
ofType(actions.add_first_file),
switchMap((_file) => this.http_service.upload_a_file(_file.file)).pipe(
map((_id) => actions.add_first_file_success({
created_file_id: _id,
}),
catchError((error: any) => of(
actions.add_first_file_failure({
error: error,
}))
)
))
);
add_second_file = createEffect(() => this.actions$.pipe(
ofType(actions.add_first_file_success),
switchMap((_file) => this.http_service.upload_a_file(_file.file)).pipe(
map((_id) => actions.add_second_file_success({
created_file_id: _id,
}),
catchError((error: any) => of(
actions.add_second_file_failure({
error: error,
}))
)
))
);
// etc for third file triggered by actions.add_second_file_success
以与示例类似的方式构造您的减速器,但单独存储每个文件 ID,即:
on(actions.add_first_file_success, (state, { created_file_id }) => ({
...state,
first_created_file_id: created_file_id,
status: 'success',
}),
on(actions.add_second_file_success, (state, { created_file_id }) => ({
...state,
second_created_file_id: created_file_id,
status: 'success',
}),
// etc for third file
不确定您想对“最终使用返回值创建一个对象”做什么,但您可以创建一个选择器和由第三次成功上传触发的另一个效果:
const selectFileObject = createSelector(
selectFirstCreatedFileId,
selectSecondCreatedFileId,
selectThirdCreatedFileId,
(firstFileId, secondFileId, thirdFileId) => ({
firstFileId, secondFileId, thirdFileId
})
);
doSomethingWithResult = createEffect(() => this.actions$.pipe(
ofType(actions.add_third_file_success),
withLatestFrom(this.store.select(selectFileObject)),
switchMap(([, fileObject]) => this.someService.someCall(fileObject))
...
);
我不知道您的完整用例,因此答案可能不完全完整,但我会尽力描述解决方案。
请记住,虽然链式操作本身并不是真正的反模式,但基于链式操作创建工作流通常被视为反模式。从您的示例来看,您似乎没有工作流程(除非文件数量可能会有所不同并且错误处理更复杂),但正如我所说,您没有描述整个故事。
从你的描述来看,我个人根本看不出使用 NGRX 的意义。 NGRX 用于全局状态管理,这里看起来你并没有改变全局状态。您想要的只是按顺序上传几个文件并将结果连接起来。从我的角度来看,文件上传过程是您的页面/组件的责任。当完成或失败时,组件将调度一个操作来更改全局状态。
如果您尝试实现异步文件上传,当用户离开时,应用程序会上传队列中的文件,重试失败的上传并显示通知“文件 A 已(未)上传”等,然后我将在与 NGRX 组合,但不与 NGRX 效果组合。