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

查看:225
本文介绍了如何缓存已请求的数据并使用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 的选定值,并且只有 user

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 助手:

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 重击操作创建器,该创建器仅在尚未加载用户的情况下才执行提取操作:

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 创建减速器. 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钩子返回选择器中的值,但还会使用 useEffect 触发 dispatch :

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

如果用户已经已加载,则先前的方法忽略了获取,但是如果用户已经正在加载,该怎么办?对于同一个用户,我们可能会同时进行多次提取.

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.

我们的州需要存储每个用户的提取状态才能解决此问题.在文档示例中,我们可以看到它们存储了键控状态对象与用户实体并排(您也可以将状态存储为实体的一部分).

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 ="nofollow noreferrer"> action meta.arg 属性:

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天全站免登陆