虽然@reduxjs/toolkit为Redux提供了开箱即用的最佳实践,但它也内置了一些强大的功能,可以极大简化Redux在复杂场景下的使用。本文将重点介绍以下进阶特性:
1.使用Immer简化不可变更新
Redux要求状态更新必须是不可变的,这意味着我们需要手动复制和更新数据,这种模式很容易出错且难以维护。@reduxjs/toolkit内置了Immer库,让我们可以像修改JavaScript对象类型数据同普通 JavaScript 对象一样去修改状态,不需要手动拷贝,数组展开等操作。
Copy code
import { createSlice } from '@reduxjs/toolkit';
const slice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo(state, action) {
// 可以像普通数组一样修改状态
state.push(action.payload);
},
toggleTodo(state, action) {
const todo = state.find(todo => todo.id === action.payload);
// 可以直接修改todo对象
todo.completed = !todo.completed;
}
}
});
Immer会自动跟踪所有对状态的修改,并基于修改产生一个全新的不可变状态副本。这使我们无需手动进行深拷贝等繁琐操作。
2.处理异步任务
Redux 本身是不支持异步操作的,如我们想在redux中从接口中取一个用户信息,在不使用@reduxjs/toolkit的时候 ,传统方式中是可以通过中间件来实现异步任务。最常用的中间件就是 redux-thunk 和 redux-saga。而在@reduxjs/toolkit中,再次做了优化,可以通过createAsyncThunk工具简化了异步操作的处理。
示例:假设我们有两个异步请求,分别获取用户列表和用户详情:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
// 获取用户列表
export const fetchUsers = createAsyncThunk('users/fetchAll', async () => {
const res = await fetch('/api/users');
return res.json();
});
// 获取用户详情
export const fetchUserDetails = createAsyncThunk('users/fetchDetails', async (userId) => {
const res = await fetch(`/api/users/${userId}`);
return res.json();
});
const usersSlice = createSlice({
name: 'users',
initialState: {
entities: {},
userDetails: null,
loading: 'idle',
},
extraReducers: (builder) => {
// 处理获取用户列表
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = 'idle';
const userData = action.payload;
state.entities = userData.reduce((obj, user) => {
obj[user.id] = user;
return obj;
}, {});
})
.addCase(fetchUsers.rejected, (state) => {
state.loading = 'idle';
})
// 处理获取用户详情
builder
.addCase(fetchUserDetails.pending, (state) => {
state.loading = 'loading';
})
.addCase(fetchUserDetails.fulfilled, (state, action) => {
state.loading = 'idle';
state.userDetails = action.payload;
})
.addCase(fetchUserDetails.rejected, (state) => {
state.loading = 'idle';
})
}
});
export const { } = usersSlice.actions;
export default usersSlice.reducer;
总结:
使用 createAsyncThunk 创建一个异步 thunk,在 extraReducers 中处理异步 action 的不同状态。相比手写 action creators 和 reducers,这种方式更加集中和简洁。
- 为每个异步请求创建一个 createAsyncThunk 在 extraReducers 中,使用 builder.addCase
- 分别处理每个异步请求的 pending/fulfilled/rejected 状态 根据需求在不同状态下更新 state
- 可以通过在组件中分发不同的异步 thunk 来触发对应的异步请求。通过这种方式,可以集中处理多个异步请求的不同状态和效果。当然,如果异步请求逻辑过于复杂,你也可以考虑使用 Redux-Saga 或 Redux-Observable 这样的库来管理异步流程。但对于大多数场景,createAsyncThunk 已经足够强大并且易于使用了。
3. 可复用选择器
@reduxjs/toolkit 中的 createSelector
类似于 Vuex 中的 getters,它们的作用和使用方式有一些相似之处。
简单来说,createSelector
主要用于创建可复用和高效的派生状态选择器(derived state selectors)。这些选择器可以基于 Redux store 中的状态计算出其他衍生数据,类似于 Vue 组件中的计算属性。
与 Vuex getters 类似,createSelector
可以让我们避免在多个组件中重复相同的衍生数据计算逻辑。它还内置了缓存和可组合的功能,可以进一步提高选择器的性能。
下面是一个简单的例子:
import { createSelector } from '@reduxjs/toolkit';
// 一个简单的选择器
const selectUsers = state => state.users.data;
// 基于 selectUsers 创建一个衍生选择器
const selectActiveUsers = createSelector(
selectUsers,
(users) => users.filter(user => user.active)
);
// 组合多个选择器
const selectActiveUserNames = createSelector(
selectActiveUsers,
(activeUsers) => activeUsers.map(user => user.name)
);
在这个例子中:
selectUsers
是一个简单的选择器函数,返回 state.users.data。selectActiveUsers
是一个衍生选择器,基于selectUsers
的结果计算出活跃用户列表。selectActiveUserNames
是另一个衍生选择器,基于selectActiveUsers
的结果计算出活跃用户的名字列表。
使用 createSelector
的好处是:
- 可以提取和复用衍生数据计算逻辑,避免重复代码。
- 内置缓存和可组合机制,只在输入发生变化时重新计算,提高性能。
- 代码结构更清晰,更易于维护和测试。
虽然 createSelector
和 Vuex getters 在实现细节上有所不同,但它们的设计理念和使用目的是类似的,都是为了更好地管理和复用衍生状态数据。
4.总结
@reduxjs/toolkit中的三个进阶用法:
- 使用Immer简化不可变更新
Redux要求状态更新必须是不可变的,传统做法需要手动拷贝对象/数组进行修改。@reduxjs/toolkit内置了Immer库,让我们可以像修改普通 JavaScript 对象一样直接修改状态,而不必手动拷贝。
const slice = createSlice({
reducers: {
addTodo(state, action) {
// 可以像普通数组一样直接修改状态
state.push(action.payload);
}
}
});
Immer会跟踪所有对状态的修改,在最后得到一个全新的不可变状态副本,简化了不可变更新的过程。
- 处理异步更新
@reduxjs/toolkit通过createAsyncThunk
简化了异步操作的处理。
const fetchData = createAsyncThunk('data/fetch', async () => {
const res = await fetch('/api/data');
return res.json();
});
const slice = createSlice({
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {...})
.addCase(fetchData.fulfilled, (state, action) => {...})
.addCase(fetchData.rejected, (state, action) => {...})
}
});
createAsyncThunk
会为异步操作自动生成 pending/fulfilled/rejected 的 action type,我们只需在 extraReducers 中处理不同状态即可,避免了手写 action creators 的繁琐。
- 创建可复用选择器
@reduxjs/toolkit提供了createSelector
来创建可组合的派生状态选择器。
const selectUserIds = state => Object.keys(state.users.entities);
const selectUserEntities = state => state.users.entities;
const selectAllUsers = createSelector(
[selectUserIds, selectUserEntities],
(userIds, userEntities) => userIds.map(id => userEntities[id])
);
createSelector
会对输入进行缓存和可组合化处理,使得选择器可以高效地重用之前的结果,提取和复用衍生数据计算逻辑。
通过这三个进阶用法,@reduxjs/toolkit进一步简化和优化了Redux的使用,提高了开发效率和代码质量。它们建立在Redux的核心理念之上,但提供了更多的抽象和工具,使得状态管理变得更加高效和可维护。