如何用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
相关产品推荐
相关产品推荐

