【问题标题】:Download a file programmatically while providing feedback to the user using Redux以编程方式下载文件,同时使用 Redux 向用户提供反馈
【发布时间】:2022-01-07 14:27:46
【问题描述】:

我有一个返回要下载的文件的后端。

目前,当用户执行某项操作时,会调度一个操作,该操作会运行 reducer export,并执行通常的技巧来下载文件。我正在使用 redux-toolkit。

const mySlice = createSlice({
    name: "my",
    initialState,
    reducers: {
        export: (state) => {
            const link = document.createElement("a")
            link.href = '...'

            document.body.appendChild(link)
            link.click()
            link.parentNode?.removeChild(link)
        },
    }
})

这很有效。但是,我想在下载时向用户提供反馈,即模式对话框。所以我在状态中定义了一个isExporting属性,并且有一个组件根据这个属性显示对话框。

但是,这样写的reducer不起作用:

    export: (state) => {
        state.isExporting = true

        const link = document.createElement("a")
        link.href = '...'

        document.body.appendChild(link)
        link.click()
        link.parentNode?.removeChild(link)

        state.isExporting = false
    },

因为我相信我引入了副作用,而且状态将在 reducer 执行后设置,而不是在两者之间。

这样做的正确方法是什么?

【问题讨论】:

    标签: reactjs react-redux redux-toolkit


    【解决方案1】:

    创建一个异步操作createAsyncThunk 并使用案例定义一个extraReducer:

    对于这个例子,我创建了异步函数下载

    const fakeDownload = async () =>
      new Promise((resolve) =>
        setTimeout(() => {
          const link = document.createElement("a");
          link.href = "...";
          document.body.appendChild(link);
          link.click();
          link.parentNode?.removeChild(link);
    
          resolve();
        }, 1000) // I set a timeout to emulate a request.
      );
    

    创建异步操作:

    export const downloadFile = createAsyncThunk(
      "download/file",
      async (_, thunkApi) => {
        try {
          return fakeDownload();
        } catch (e) {
          return thunkApi.rejectWithValue("Impossible to download");
        }
      }
    );
    

    减速器:

    const initialState = {
      isExporting: false
    };
    
    export const appSlice = createSlice({
      name: "app",
      initialState,
      reducers: {},
      extraReducers: (builder) => {
        builder.addCase(downloadFile.pending, (state) => {
          state.isExporting = true;
        });
        builder.addCase(downloadFile.fulfilled, (state) => {
          state.isExporting = false;
        });
        builder.addCase(downloadFile.rejected, (state) => {
          // Do something with error
          state.isExporting = false;
        });
      }
    });
    
    export const isExportingSelector = (state) => state.app.isExporting;
    
    export default appSlice.reducer;
    

    现在您可以使用选择器和调度操作了。

    你可以在这里找到一个活生生的例子:

    【讨论】:

    • 谢谢!试过了,只有一件事不起作用:当click() 实际执行时,我可以在网络选项卡中看到已请求下载,但是承诺立即得到解决。这让我觉得click() 不会等待下载完成。如何等待下载,以便下载完成后promise调用resolve()
    • @edoedoedo 可以分享下载请求的代码吗?
    • 当然:codesandbox.io/s/xenodochial-meadow-mx0ln?file=/src/store/… 我刚刚编辑了你的函数来实际下载一些东西。例如,使用不存在的 url,您将在网络选项卡中看到请求持续几秒钟然后超时(这当然不重要,重要的是它需要几秒钟)但在控制台中您会看到承诺立即完成:redux-logger.js:1 action download/file/pending @ 16:53:35.833 redux-logger.js:1 action download/file/fulfilled @ 16:53:35.846
    • @edoedoedo 看看函数downloadcodesandbox.io/s/bold-lake-otpp7?file=/src/store/slices/…我只是编辑函数
    • 它运行良好,完全符合我的预期。我还学到了一些关于如何在 RTK 中链接减速器的新知识,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-09
    相关资源
    最近更新 更多