如何缓存已请求的数据并使用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从存储中获取数据,并在已经请求的情况下获取缓存版本?
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 允许我们在结构化结构中存储和选择类似于用户对象的实体基于唯一ID的方式.
- createAsyncThunk 将创建从API提取数据的thunk操作. li>
- createSlice 或
- 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屋!