File upload 文件上传的Redux thunk设计,包括取消和进度
想要在react redux中上传一些文件,我有以下想法:File upload 文件上传的Redux thunk设计,包括取消和进度,file-upload,redux,functional-programming,axios,redux-thunk,File Upload,Redux,Functional Programming,Axios,Redux Thunk,想要在react redux中上传一些文件,我有以下想法: 设置redux thunkuploadFile操作,以文件描述符作为参数启动上载 定义我自己的可在存储中序列化的“文件描述符”(具有uuid、pending、sent、error和pending属性 设置一些其他FSA,如addFile,removeFile,setfileerred,setFileSent,setFileSent 像这样 减速器 行动 注意:上传文件是我的助手,包装了axios承诺。 第一个参数是文件描述符,第二个
- 设置redux thunk
操作,以uploadFile
描述符作为参数启动上载文件
- 定义我自己的可在存储中序列化的“文件描述符”(具有
、uuid
、pending
、sent
和error
属性pending
- 设置一些其他FSA,如
,addFile
,removeFile
,setfileerred
,setFileSent
setFileSent
上传文件
是我的助手,包装了axios承诺。
第一个参数是文件
描述符,第二个参数是axios选项对象
我认为这应该管用
但现在我正为一些设计问题而挣扎:
- 这是正确的方法吗?我的意思是:
- 这充满了杂质,但ajax查询本质上是不纯净的
- 我完全失去了
描述符引用,因此不允许以后访问它(例如用于文件
)。我将它存储在哪里?我发现将它存储在存储中非常糟糕,主要是因为我们不能用ES6的东西纯粹更新预览
描述符,因此我们需要对它进行变异文件
- Axios提供了一个简洁的
功能,我可以将其传递给我的选项。我以前在React中使用过它,但切换到redux时,同样的问题出现了:如果我在CancelToken
中定义了uploadFile()
,我应该将其存储在哪里,这样我就可以在另一个目录中访问它,比如说,CancelToken
thunk动作cancelFileUpload(fileId)
filter()
调用
在动作设计上,我猜您正在构建一个上传队列,因此您需要队列
/发送挂起
/发送进度
/发送被拒绝
/发送已完成
(为了清晰起见,我用命名方法对它们进行了重新表述)。在这里您无法逃脱任何东西
在action factory上,因为Promise没有进度事件,所以它现在看起来有点笨重。您可以尝试使用redux saga
实现。代码看起来稍微干净一点。但是处理多个事件变得很容易。但是那里有一个学习曲线。代码应该类似于下面的内容。关注while循环现在,还要查看取消处理(通过cancel
操作)
要上载文件,请分派队列
操作。要取消上载文件,只需分派取消
操作
import { call, put, race, takeEvery } from 'redux-saga/effects';
import EventAsPromise from 'event-as-promise';
yield takeEvery('QUEUE', function* (action) {
yield put({ type: 'SEND_PENDING' });
const { file } = action.payload;
const progress = new EventAsPromise();
const donePromise = uploadFile(file, progress.eventListener);
try {
while (true) {
// Wait until one of the Promise is resolved/rejected
const [progress] = yield race([
// New progress event came in
call(progress.upcoming),
// File upload completed promise resolved or rejected
call(() => donePromise),
// Someone dispatched 'CANCEL'
take(action => action.type === 'CANCEL' && action.payload.file === file)
]);
if (progress) {
// Progress event come first, so process it here
yield put({
type: 'SEND_PROGRESS',
payload: {
loaded: progress.loaded,
total: progress.total
}
});
} else {
// Either done or cancelled (use your cancel token here)
// Breaking a while-true loop isn't really looks good, but there aren't any better options here
break;
}
}
// Here, we assume CANCEL is a kind of fulfillment and do not require retry logic
yield put({ type: 'SEND_FULFILLED' });
} catch (err) {
yield put({ type: 'SEND_REJECTED', error: true, payload: err });
}
});
由于redux saga
只接受承诺或行动,为了将事件流转换为承诺,我在这里介绍
使用redux saga
,流程控制(进度/完成/错误/取消)变得非常清晰,不太容易出现错误。在您的情况下,我使用redux saga
的方式就像文件上传的生命周期管理器。安装和拆卸总是成对进行的。如果拆卸调用(完成/错误/取消),您无需担心没有正确地开火
您需要了解有关文件描述符的情况。尽量不要将其放在存储中,因为这会阻止存储被持久化,您可能希望跨页面导航或应用程序重新启动持久化存储。根据您的场景,如果用于重试上载,您可以将其临时存储在传奇中的闭包中
在本例中,您可以轻松地将其扩展到一个上传队列,一个一个地上传文件。如果您精通
redux saga
,这不是一项困难的任务,应该少于10行代码更改。如果没有redux saga
,实现这一点将是一项相当大的任务。疯狂的回答,但正如您所提到的,确实存在一个学习曲线:P我需要时间来检查这个伟大的东西。同时,你会回答:代码生成器是绑定的,还是这个承诺是可翻译的?我对生成器非常在行,而且更像是一个承诺者,也许这可以让学习曲线变得更清晰一点:X你能解释一下关于“生成器绑定”的更多信息吗“承诺可翻译”?Redux传奇使用生成器来维护“工作流”。通过使用redux saga/effects/call
函数,从生成器到Promise有一个转义图案。但是没有事件转义图案,因此我们需要将事件转换为Promise。我的意思是您提供的代码完全基于生成器。我对redux saga进行了一些挖掘,并试图理解这些生成器的内容。我很惊讶ng如果必须使用生成器,或者只有承诺才能实现这一点,那么就必须使用生成器。当我开始讲述redux saga时,我也考虑过将一切都变成承诺,我认为这是遗留问题。但事实上,redux saga使用生成器使用闭包来“保存工作流”。因此redux saga始终可以“继续它离开的地方”。非常聪明的技巧。但缺点是:你不能持久化工作流或恢复工作流。好的,谢谢。我接受这一点,即使redux saga不是我过去的方式。语法对我来说太晦涩了,导致可维护性降低。我现在使用redux logic,并考虑使用重新匹配或更简单的方法来解决副作用,逻辑a一切
// skipping static actions
export const uploadFile = (actualFile) => {
const file = {
id: uuidv4(),
sending: false,
sent: false,
errored: false
}
return (dispatch) => {
dispatch(addFile({
...file,
sending: true
}))
return uploadFile(actualFile, {
onUploadProgress: (evt) => {
if(evt.loaded && evt.total) {
const progress = (evt.loaded / evt.total) * 100
dispatch(setFileProgress(file.id, progress))
}
}
})
.then((fileUrl) => {
dispatch(setFileSent(file.id))
dispatch(setFileUrl(file.id, url))
})
.catch((err) => {
console.log(err)
dispatch(setFileErrored(file.id))
})
}
}
import { call, put, race, takeEvery } from 'redux-saga/effects';
import EventAsPromise from 'event-as-promise';
yield takeEvery('QUEUE', function* (action) {
yield put({ type: 'SEND_PENDING' });
const { file } = action.payload;
const progress = new EventAsPromise();
const donePromise = uploadFile(file, progress.eventListener);
try {
while (true) {
// Wait until one of the Promise is resolved/rejected
const [progress] = yield race([
// New progress event came in
call(progress.upcoming),
// File upload completed promise resolved or rejected
call(() => donePromise),
// Someone dispatched 'CANCEL'
take(action => action.type === 'CANCEL' && action.payload.file === file)
]);
if (progress) {
// Progress event come first, so process it here
yield put({
type: 'SEND_PROGRESS',
payload: {
loaded: progress.loaded,
total: progress.total
}
});
} else {
// Either done or cancelled (use your cancel token here)
// Breaking a while-true loop isn't really looks good, but there aren't any better options here
break;
}
}
// Here, we assume CANCEL is a kind of fulfillment and do not require retry logic
yield put({ type: 'SEND_FULFILLED' });
} catch (err) {
yield put({ type: 'SEND_REJECTED', error: true, payload: err });
}
});