如何缓存我已经请求的数据并使用 React 和 Redux Toolkit 从商店访问它

How can I cache data that I already requested and access it from the store using React and Redux Toolkit

我如何使用 React Redux Toolkit 从商店获取数据并在我已经请求的情况下获取缓存版本?

我需要请求多个用户,例如 user1、user2 和 user3。如果我在请求 user1 之后发出请求,那么我不想再次从 API 中获取 user1。相反,它应该给我商店中 user1 的信息。

如何使用 Redux Toolkit 切片在 React 中执行此操作?

编辑:这个答案早于 RTK Query which has made this task much easier! RTK Query automatically handles caching and much more. Check out the docs 发布如何设置。

如果您有兴趣进一步了解其中的一些概念,请继续阅读。


工具

Redux Toolkit 可以帮助解决这个问题,但我们需要在工具包中组合各种“工具”。

React 与 Redux

我们将创建一个 useUser 自定义 React 挂钩以通过 ID 加载用户。

我们需要在 hooks/components 中使用单独的挂钩来读取数据 (useSelector) 和启动提取 (useDispatch)。存储用户状态永远是 Redux 的工作。除此之外,我们在 React 或 Redux 中处理某些逻辑方面还有一些余地。

我们可以在自定义挂钩中查看 user 的 selected 值,如果 userundefined。或者我们可以一直 dispatch requestUser 并让 requestUser thunk 检查它是否需要使用 condition setting of createAsyncThunk.

进行提取

基本方法

我们天真的方法只是检查用户是否已经存在于状态中。我们不知道此用户的任何其他请求是否已处于待处理状态。

让我们假设您有一些函数接受一个 id 并获取用户:

const fetchUser = async (userId) => {
    const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
    return res.data;
};

我们创建一个 userAdapter 助手:

const userAdapter = createEntityAdapter();

// needs to know the location of this slice in the state
export const userSelectors = userAdapter.getSelectors((state) => state.users);

export const { selectById: selectUserById } = userSelectors;

我们创建了一个 requestUser thunk 动作创建器,它仅在用户尚未加载时才执行提取:

export const requestUser = createAsyncThunk("user/fetchById", 
  // call some API function
  async (userId) => {
    return await fetchUser(userId);
  }, {
    // return false to cancel
    condition: (userId, { getState }) => {
        const existing = selectUserById(getState(), userId);
        return !existing;
    }
  }
);

我们可以使用 createSlice 来创建减速器。 userAdapter 帮助我们更新状态。

const userSlice = createSlice({
    name: "users",
    initialState: userAdapter.getInitialState(),
    reducers: {
    // we don't need this, but you could add other actions here
    },
    extraReducers: (builder) => {
        builder.addCase(requestUser.fulfilled, (state, action) => {
            userAdapter.upsertOne(state, action.payload);
        });
    }
});
export const userReducer = userSlice.reducer;

但是由于我们的 reducers 属性 是空的,我们也可以使用 createReducer:

export const userReducer = createReducer(
    userAdapter.getInitialState(),
    (builder) => {
        builder.addCase(requestUser.fulfilled, (state, action) => {
            userAdapter.upsertOne(state, action.payload);
        });
    }
)

我们的 React hook returns 来自 selector 的值,而且还会触发一个 dispatch 和一个 useEffect:

export const useUser = (userId: EntityId): User | undefined => {
  // initiate the fetch inside a useEffect
  const dispatch = useDispatch();
  useEffect(
    () => {
      dispatch(requestUser(userId));
    },
    // runs once per hook or if userId changes
    [dispatch, userId]
  );

  // get the value from the selector
  return useSelector((state) => selectUserById(state, userId));
};

正在加载

如果用户已经 loaded,之前的方法会忽略抓取,但是如果已经 loading 呢?我们可以同时对同一用户进行多次提取。

为了解决这个问题,我们的状态需要存储每个用户的获取状态。在 docs example 中,我们可以看到它们将状态的键控对象与用户实体一起存储(您也可以将状态存储为实体的一部分)。

我们需要在 initialState:

上添加一个空的 status 字典作为 属性
const initialState = {
  ...userAdapter.getInitialState(),
  status: {}
};

我们需要更新状态以响应所有三个 requestUser 操作。我们可以通过查看 action:

meta.arg 属性 来获取调用 thunk 的 userId
export const userReducer = createReducer(
  initialState,
  (builder) => {
    builder.addCase(requestUser.pending, (state, action) => {
      state.status[action.meta.arg] = 'pending';
    });
    builder.addCase(requestUser.fulfilled, (state, action) => {
      state.status[action.meta.arg] = 'fulfilled';
      userAdapter.upsertOne(state, action.payload);
    });
    builder.addCase(requestUser.rejected, (state, action) => {
      state.status[action.meta.arg] = 'rejected';
    });
  }
);

我们可以 select 来自 id 的状态的状态:

export const selectUserStatusById = (state, userId) => state.users.status[userId];

我们的 thunk 在确定是否应该从 API 中获取时应该查看状态。如果它已经是 'pending''fulfilled',我们不想加载。如果是 'rejected'undefined:

我们将加载
export const requestUser = createAsyncThunk("user/fetchById", 
    // call some API function
    async (userId) => {
        return await fetchUser(userId);
    }, {
        // return false to cancel
        condition: (userId, { getState }) => {
            const status = selectUserStatusById(getState(), userId);
            return status !== "fulfilled" && status !== "pending";
        }
    }
);