如何缓存我已经请求的数据并使用 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 可以帮助解决这个问题,但我们需要在工具包中组合各种“工具”。
- createEntityAdapter 允许我们基于唯一 ID 以结构化方式存储和 select 实体,例如用户对象。
- createAsyncThunk 将创建从 API.
中获取数据的 thunk 操作
- createSlice or createReducer 创建我们的减速器。
React 与 Redux
我们将创建一个 useUser
自定义 React 挂钩以通过 ID 加载用户。
我们需要在 hooks/components 中使用单独的挂钩来读取数据 (useSelector
) 和启动提取 (useDispatch
)。存储用户状态永远是 Redux 的工作。除此之外,我们在 React 或 Redux 中处理某些逻辑方面还有一些余地。
我们可以在自定义挂钩中查看 user
的 selected 值,如果 user
是 undefined
。或者我们可以一直 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";
}
}
);
我如何使用 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 可以帮助解决这个问题,但我们需要在工具包中组合各种“工具”。
- createEntityAdapter 允许我们基于唯一 ID 以结构化方式存储和 select 实体,例如用户对象。
- createAsyncThunk 将创建从 API. 中获取数据的 thunk 操作
- createSlice or createReducer 创建我们的减速器。
React 与 Redux
我们将创建一个 useUser
自定义 React 挂钩以通过 ID 加载用户。
我们需要在 hooks/components 中使用单独的挂钩来读取数据 (useSelector
) 和启动提取 (useDispatch
)。存储用户状态永远是 Redux 的工作。除此之外,我们在 React 或 Redux 中处理某些逻辑方面还有一些余地。
我们可以在自定义挂钩中查看 user
的 selected 值,如果 user
是 undefined
。或者我们可以一直 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";
}
}
);