以编程方式下载文件,同时使用 Redux 向用户提供反馈

Download a file programmatically while providing feedback to the user using Redux

我有一个后端,returns 一个要下载的文件。

目前,当用户做某事时,会调度一个动作来运行减速器 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 执行之后设置,而不是在两者之间设置。

正确的做法是什么?

创建一个异步操作 createAsyncThunk 并定义一个带有案例的 extraReducer:

For this example i create async function download

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;

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

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