You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用useEffect取消含两个dispatch的onClick旧事件处理函数?

Redux Toolkit createAsyncThunk 取消异步任务问题及解决

需求与初始问题

需要实现:用户快速点击不同列表项时,取消之前正在运行的fetchFileContents请求,始终只展示最新请求的结果。初始代码中点击事件处理函数包含两个dispatch操作,尝试用useCallback包裹并配合带清理函数的useEffect实现取消,出现异常行为。

初始代码片段:

const fetchFileContentsHandler = () => {
  dispatch(fetchFileContents(props.params));
  dispatch(
    changeSelectedFile({ selectedFile: props.params, selected: true })
  );
};

return (
  <NavLink to="#" key={fileName}>
    <ListItemButton
      selected={props.selected}
      onClick={fetchFileContentsHandler}
    >
      <ListItemText
        primary={fileName}
      />
    </ListItemButton>
  </NavLink>
);
};

export default Items;

第一次尝试:改用createAsyncThunk + useRef.abort()

将fetchFileContents改为createAsyncThunk,并添加useRef存储请求Promise,调用.abort()尝试取消旧请求,但未生效——旧请求完成后仍会更新状态,导致界面先显示新内容后又被旧内容覆盖。

slice.js代码:

export const fetchFileContents = createAsyncThunk(
  "ui/getFileContents",
  async (fileName) => {
    const fileContentsResponse = await fetch(...);
    const response = await fileContentsResponse.json();

    return {
      fileType: fileName.includes("html") ? "text" : "html",
      contents: data,
    };
  }
);
const Slice = createSlice({
  name: "somename",
  initialState: {
    htmlReport: null,
    textReport: null,
  },
  reducers: {
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchFileContents.fulfilled, (state, action) => {
        if (action.payload.fileType === "text") {
          state.textReport = action.payload.contents;
          state.htmlReport = null;
        } else if (action.payload.fileType === "html") {
          state.textReport = null;
          state.htmlReport = action.payload.contents;
        }
      })
      .addCase(fetchFileContents.rejected, (state, action) => {
        console.log("[REJECTED]: ");
        const { fileName } = action.meta.arg;
        const { message } = action.error;
        console.log(fileName, message);
      });
  },
});

Component.js代码:

const fetchFileContentsPromise = useRef();

const fetchFileContentsHandler = () => {
  fetchFileContentsPromise.current?.abort();

  fetchFileContentsPromise.current = dispatch(
    fetchFileContents({ fileName: props.params })
  );
  dispatch(
    changeSelectedFile({ selectedFile: props.params, selected: true })
  );
};

日志显示旧请求仍会触发fulfilled状态:

undefined
Fetching data with A...
undefined
Fetching data with C...
[FULFILLED]: CCC
[Violation] 'setTimeout' handler took 55ms
[FULFILLED]: AAA

最终解决:将useRef移至父组件传递

将const fetchFileContentsPromise = useRef();声明在父组件中,通过props传递给子组件及孙组件后,取消功能生效。

原因解释

每个React组件实例都拥有独立的useRef引用。如果在每个子组件中单独声明fetchFileContentsPromise,那么每个子组件的ref只能管理自己发起的请求,无法访问和取消其他子组件发起的旧请求。而父组件的useRef是整个组件树中唯一的共享引用,所有子组件使用同一个ref存储请求Promise,每次新请求发起时,都能通过这个ref取消之前任何子组件发起的未完成请求,确保只有最新的请求结果会更新状态。

内容的提问来源于stack exchange,提问作者BRICKLEBERRYoverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 21:27:28