如何缓存我已经请求的数据并使用 React 和 Redux Toolkit 从商店访问它 [英] How can I cache data that I already requested and access it from the store using React and Redux Toolkit

查看:17
本文介绍了如何缓存我已经请求的数据并使用 React 和 Redux Toolkit 从商店访问它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用 React Redux Toolkit 从商店获取数据并获取缓存版本(如果我已经请求)?

How can I get data from the store using React Redux Toolkit and get a cached version if I already requested it?

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

I need to request multiple users for example user1, user2, and user3. If I make a request for user1 after it has already been requested then I do not want to fetch user1 from the API again. Instead it should give me the info of the user1 from the store.

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

How can I do this in React with a Redux Toolkit slice?

推荐答案

工具

Redux Toolkit 可以帮助解决这个问题,但我们需要结合各种工具"在工具包中.

Tools

Redux Toolkit can help with this but we need to combine various "tools" in the toolkit.

  • createEntityAdapter allows us to store and select entities like a user object in a structured way based on a unique ID.
  • createAsyncThunk will create the thunk action that fetches data from the API.
  • createSlice or createReducer creates our reducer.

我们将创建一个 useUser 自定义 React 钩子以通过 id 加载用户.

We are going to create a useUser custom React hook to load a user by id.

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

We will need to use separate hooks in our hooks/components for reading the data (useSelector) and initiating a fetch (useDispatch). Storing the user state will always be the job of Redux. Beyond that, there is some leeway in terms of whether we handle certain logic in React or in Redux.

我们可以在自定义钩子中查看 user 的选定值,并且只有 dispatch 如果 user 动作代码> 是<代码>未定义.或者我们可以一直 dispatch requestUser 并让 requestUser thunk 检查它是否需要使用 condition createAsyncThunk 的设置.

We could look at the selected value of user in the custom hook and only dispatch the requestUser action if user is undefined. Or we could dispatch requestUser all the time and have the requestUser thunk check to see if it needs to do the fetch using the condition setting of createAsyncThunk.

我们的简单方法只是检查用户是否已经存在于状态中.我们不知道是否有任何其他针对此用户的请求已待处理.

Our naïve approach just checks if the user already exists in the state. We don't know if any other requests for this user are already pending.

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

Let's assume that you have some function which takes an id and fetches the user:

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

我们创建了一个 userAdapter 助手:

We create a userAdapter helper:

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 动作创建者,它只在用户尚未加载时执行获取:

We create a requestUser thunk action creator that only executes the fetch if the user is not already loaded:

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来创建reducer.userAdapter 帮助我们更新状态.

We can use createSlice to create the reducer. The userAdapter helps us update the state.

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:

But since our reducers property is empty, we could just as well use createReducer:

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

我们的 React 钩子从选择器返回值,但也会触发一个带有 useEffectdispatch:

Our React hook returns the value from the selector, but also triggers a dispatch with a 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));
};

正在加载

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

isLoading

The previous approach ignored the fetch if the user was already loaded, but what about if it is already loading? We could have multiple fetches for the same user occurring simultaneously.

为了解决这个问题,我们的 state 需要存储每个用户的 fetch 状态.在 docs 示例 中,我们可以看到它们存储了一个键控对象用户实体旁边的状态(您也可以将状态存储为实体的一部分).

Our state needs to store the fetch status of each user in order to fix this problem. In the docs example we can see that they store a keyed object of statuses alongside the user entities (you could also store the status as part of the entity).

我们需要在 initialState 上添加一个空的 status 字典作为属性:

We need to add an empty status dictionary as a property on our initialState:

const initialState = {
  ...userAdapter.getInitialState(),
  status: {}
};

我们需要更新状态以响应所有三个 requestUser 操作.我们可以通过查看 userId="noreferrer">meta.arg action 的属性:

We need to update the status in response to all three requestUser actions. We can get the userId that the thunk was called with by looking at the meta.arg property of the action:

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';
    });
  }
);

我们可以通过id从状态中选择一个状态:

We can select a status from the state by id:

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

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

Our thunk should look at the status when determining if it should fetch from the API. We do not want to load if it is already 'pending' or 'fulfilled'. We will load if it is 'rejected' or 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";
        }
    }
);

这篇关于如何缓存我已经请求的数据并使用 React 和 Redux Toolkit 从商店访问它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆