在 React Redux 中,如何重用执行 fetch 的钩子,而无需多次执行该 fetch?

问题描述 投票:0回答:2

我在组件中使用一个钩子来访问通过 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
,但这并不理想,因为我希望子组件独立于父组件,并且能够在其他组件中重用它们,而不必担心资源是否已获取。

javascript reactjs redux react-redux redux-thunk
2个回答
1
投票

我实际上不确定为什么你的代码不起作用,但这个钩子看起来有点复杂。我以前写过类似的代码,所以我认为这样的代码应该可以解决问题:

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()
之前检查。那么动作就不会被调度,这实际上更干净。我把这个留给你。


0
投票

我相信你应该研究一下 Redux Toolkit Query

RTK Query 是一个强大的数据获取和缓存工具。它旨在简化在 Web 应用程序中加载数据的常见情况,无需您自己手动编写数据获取和缓存逻辑。数据获取和缓存逻辑构建在 Redux Toolkit 的

createSlice
createAsyncThunk
API 之上。

© www.soinside.com 2019 - 2024. All rights reserved.