繁体   English   中英

用于文件上传的Redux thunk设计,包括取消和进度

[英]Redux thunk design for file upload, including cancel and progress

想要在react-redux中上传一些文件,我有以下想法:

  • 设置一个redux-thunk的uploadFile操作,以File描述符作为参数来启动上传
  • 确定自己的“文件描述符”在店里序列化(用uuid ,一个pending ,一sent ,一erroredpending性质
  • 设置其他一些FSA,例如addFileremoveFilesetFileErroredsetFileSentsetFileSent

像这样

减速器

  switch (action.type) {
    case FILES__ADD_FILE:
      return {
        ...state,
        files: [
          ...state.files,
          action.payload
        ]
      }
    case FILES__REMOVE_FILE:
      return {
        ...state,
        files: state.files.filter(
          file => file.id !== action.payload
        )
      }
    case FILES__SET_FILE_ERRORED:
      return {
        ...state,
        files: state.files.map(file => {
          if(file.id !== action.payload.fileId) {
            return file
          }

          return {
            ...file,
            sending: false,
            errored: true,
            sent: false
          }
        })
      }
    case FILES__SET_FILE_SENDING:
      return {
        ...state,
        files: state.files.map(file => {
          if(file.id !== action.payload) {
            return file
          }

          return {
            ...file,
            sending: true,
            errored: false,
            sent: false
          }
        })
      }
    case FILES__SET_FILE_SENT:
      return {
        ...state,
        files: state.files.map(file => {
          if(file.id !== action.payload) {
            return file
          }

          return {
            ...file,
            sending: false,
            errored: false,
            sent: true
          }
        })
      }
    case FILES__SET_FILE_PROGRESS:
      return {
        ...state,
        files: state.files.map(file => {
          if(file.id !== action.payload.fileId) {
            return file
          }

          return {
            ...file,
            progress: action.payload.progress
          }
        })
      }
    default:
      return state
  }

行动

// 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))
    })
  }
}

注意uploadFile是我的助手,包装了axios承诺。 第一个参数是一个File描述符,第二个是一个Axios公司选项对象。

我认为应该可以。

但是现在我在努力解决一些设计问题:

  • 那是正确的方法吗? 我的意思是:
    • 这充满了杂质,但从本质上讲ajax查询是不纯的。
    • 我完全失去了File描述符引用,因此不允许以后再访问它(例如,进行preview )。 我将其存储在哪里? 我发现很难将其存储在商店中,主要是因为我们不能单纯使用ES6来更新File描述符,因此我们需要对其进行突变
  • Axios提供了一个简洁的CancelToken东西,我可以将其传递给我的选项。 我以前在React中使用过它,但是切换到redux时,我遇到了同样的问题:如果我在uploadFile()定义了cancelToken ,我将其存储在哪里,这样我就可以在另一个内部(例如cancelFileUpload(fileId)访问它cancelFileUpload(fileId)动作?

在减速器方面,我认为您的动作很好(点差/休息时为+1)。 但是通过使用一些诸如“ simple-update-in ”之类的“ update-in”库,代码结构看起来会更好一些。 它可以帮助您消除一些filter()调用。

在动作设计上,我想您正在构建一个上传队列,因此您需要使用QUEUE / SEND_PENDING / SEND_PROGRESS / SEND_REJECTED / SEND_FULFILLED (为清楚起见,我使用redux-promise-middleware命名方法对其进行了重新SEND_FULFILLED 。)

在动作工厂中,因为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 });
  }
});

要上传文件,请调度QUEUE操作。 要取消上传文件,只需调度CANCEL操作即可。

由于redux-saga仅接受Promise或action,为了将事件流转换为Promise,我在这里介绍了event-as-promise

使用redux-saga ,流程控制(进度/完成/错误/取消)变得非常清晰,并且不易出现错误。 就您而言,我使用redux-saga的方式就像文件上传的生命周期管理器一样。 设置和拆卸总是成对出现。 您也不必担心拆解呼叫(完成/错误/取消)是否被正确触发。

您需要弄清楚有关文件描述符的故事。 尽量不要将其放置在商店中,因为它会阻止商店被保留,您可能需要在页面导航或应用程序重新启动过程中保留商店。 根据您的方案,如果将其用于重试上传,则可以将其临时存储在传奇中的闭包中。

在此示例中,您可以轻松地将其扩展到一个上传队列,该队列一个接一个地上传文件。 如果您精通redux-saga ,这应该不是一件容易的事,应该少于10行代码更改。 没有redux-saga ,要实现它将是一项艰巨的任务。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM