我在组件中使用一个钩子来访问通过 Ajax 获取并保存在 Redux 存储中的外部资源。我想要的是,如果资源尚未在存储中,则该挂钩能够处理获取资源,但如果在,则简单地返回资源数据。但是,我无法让它工作,因为每次我在不同的组件中调用钩子时都会执行获取。
这就是我的 Redux 切片的样子:
export const fetchResource = createAsyncThunk(
'slice/resource',
async () => {
const { data } = await axios.get(
'api.com/resource',
);
return data;
},
);
const slice = createSlice({
name: 'slice',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchResource.pending, (state) => {
state.resource.status = 'loading';
})
.addCase(fetchResource.fulfilled, (state, action) => {
state.resource.status = 'success';
state.resource.data = action.payload;
})
.addCase(fetchResource.rejected, (state) => {
state.resource.status = 'error';
});
},
});
这就是我的钩子:
export function useResource(initialFetch = false) {
const dispatch = useDispatch();
const status = useSelector(state => state.resource.status);
const resource = useSelector(state => state.resource.resource);
const refreshResource = useCallback(() => {
dispatch(fetchResource());
}, [dispatch]);
useEffect(() => {
if (initialFetch && status === 'idle') refreshResource();
}, [initialFetch, status, refreshResource]);
return {
status,
resource,
refreshResource,
}
}
这个钩子的意思是,如果我在任何组件中调用
useResource(true)
,钩子将自动获取资源并在存储中丢失时返回它,或者如果找到则简单地返回它。作为旁注,我将调度包装在 refreshResource
中,以便我可以在钩子中返回此函数,以便组件稍后能够在需要时尝试重新加载此资源。
但是,假设我有组件
Child1
和 Child2
,每个组件都包含对 useResource(true)
的调用,然后我有一个同时使用 Parent
和 Child1
的 Child2
组件,观察到的结果是资源被获取两次。我认为这不会发生,因为我添加了一些逻辑,以便仅在获取状态为 idle
时才获取资源,并且所有组件都应该查看并修改相同的状态,对吗?
为什么这不起作用?有什么方法可以解决它吗?
此外,这不是由 StrictMode 引起的,就好像我将对
useResource(true)
的两个调用中的任何一个更改为 useResource(false)
一样,我确实只得到了一次 fetch。
我现在使用的解决方法是在子组件中调用
useResource(false)
并在 useResource(true)
组件中调用 Parent
,但这并不理想,因为我希望子组件独立于父组件,并且能够在其他组件中重用它们,而不必担心资源是否已获取。
我实际上不确定为什么你的代码不起作用,但这个钩子看起来有点复杂。我以前写过类似的代码,所以我认为这样的代码应该可以解决问题:
export const fetchResource = createAsyncThunk(
'slice/resource',
async (arg, thunkAPI) => {
const state = thunkAPI.getState();
const { data, status } = state.resource;
if (!data && status !== 'loading') {
// Store is empty and there is no other pending request.
const response = await axios.get(
'api.com/resource',
);
return response.data;
}
return data;
},
);
export function useResource() {
const dispatch = useDispatch();
const { data, status } = useSelector(state => state.resource);
const refreshResource = () => dispatch(fetchResource());
useEffect(() => {
refreshResource();
}, []);
return {
status,
data,
refreshResource,
}
}
如果存储为空,效果将进行初始提取。顺便说一句,您不需要将
dispatch
添加到依赖项数组中,它是一个稳定的引用。另一种选择是询问“它已经在商店里了吗?”在效果中调用 refreshResource()
之前检查。那么动作就不会被调度,这实际上更干净。我把这个留给你。
我相信你应该研究一下 Redux Toolkit Query。
RTK Query 是一个强大的数据获取和缓存工具。它旨在简化在 Web 应用程序中加载数据的常见情况,无需您自己手动编写数据获取和缓存逻辑。数据获取和缓存逻辑构建在 Redux Toolkit 的
createSlice
和 createAsyncThunk
API 之上。