06 - 状态管理
Store 创建
单例模式
src/store/index.js 使用单例模式,全局只有一个 Store 实例:
js
import { configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import logger from 'redux-logger'
import reducer from './reducer'
import storage from './storage'
let store, persistor
export default function configStore(preloadedState = {}) {
if (!store) {
store = configureStore({
reducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: { ignoredActions: ['persist/PERSIST'] }
}).concat(logger),
preloadedState
})
persistor = persistStore(store)
}
return { store, persistor }
}redux-logger中间件用于开发环境日志serializableCheck忽略persist/PERSISTaction(redux-persist 内部会 dispatch 非序列化数据)
Reducer 组合
12 个 Slice
js
import { combineReducers } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from './storage'
const rootReducer = combineReducers({
guide: guideReducer, // 导购
user: userReducer, // 用户
colors: colorsReducer, // 颜色
sys: sysReducer, // 系统配置
cart: cartReducer, // 购物车
merchant: merchantReducer, // 商户入驻
shop: shopReducer, // 店铺
tabBar: tabBar, // TabBar
community: communityReducer, // 社区团购
dianwu: dianwuReducer, // 店务
purchase: purchaseReducer, // 内购
member: memberReducer // 会员
})redux-persist 持久化
配置
js
const reducer = persistReducer({
key: 'root',
storage,
blacklist: ['merchant', 'select', 'sys'], // 不持久化
throttle: 20 // 20ms 节流
}, rootReducer)不持久化的 Slice
| Slice | 原因 |
|---|---|
merchant | 商户入驻状态敏感,不持久化 |
select | 社区团购选品为临时状态 |
sys | 系统配置每次启动从服务端拉取 |
注意:
communitySlice 的 name 是'select',blacklist 中用select匹配。
Storage 适配器
weapp(自定义 Taro Storage 适配器):
js
// src/store/storage.js
import Taro from '@tarojs/taro'
export default {
getItem(key) {
return new Promise(resolve => resolve(Taro.getStorageSync(key)))
},
setItem(key, data) {
return Taro.setStorage({ key, data })
},
removeItem(key) {
return Taro.removeStorage({ key })
},
clear() {
return Taro.clearStorage()
}
}h5(使用 redux-persist 内置的 localStorage):
js
import storage from 'redux-persist/lib/storage'各 Slice 详解
user Slice(用户状态)
js
initialState = {
userInfo: null, // 用户完整信息
isNewUser: false, // 是否新用户
cardInfo: {}, // 卡信息
lang: 'en', // 语言
vipInfo: { // 会员信息
isOpen: false,
isVip: false,
vipType: '',
endTime: ''
},
showAdv: false, // 是否显示广告
favs: [], // 收藏列表
location: null, // 当前定位地址
chiefInfo: {}, // 团长信息
checkIsChief: true, // 是否检查团长
address: null // 用户选中的收货地址
}Async Thunks:
fetchUserFavs(params)— 获取收藏列表addUserFav(itemId)— 添加收藏deleteUserFav(itemId)— 删除收藏
Reducers:
updateUserInfo— 更新用户信息、VIP、收藏、余额updateIsNewUser— 新用户标记updateChooseAddress— 选中地址updateLocation— 定位信息updateLang— 语言切换closeAdv— 关闭广告clearUserInfo— 清空所有用户信息updateCheckChief— 团长信息
cart Slice(购物车状态)
js
initialState = {
cartCount: 0, // 购物车数量
cartSalesman: 0, // 业务员购物车数量
validCart: [], // 有效购物车项
invalidCart: [], // 失效购物车项
validSalesmanCart: [], // 业务员有效购物车
invalidSalesmanCart: [], // 业务员失效购物车
customerLnformation: {}, // 客户信息
customerSalesman: {}, // 业务员客户信息
coupon: null, // 选中的优惠券
zitiAddress: null, // 自提地址
shopCartCount: {}, // 店铺级购物车数量
shopSalesmanCartCount: {}, // 店铺级业务员数量
deliveryPersonnel: { // 配送员
self_delivery_operator_id: [],
distributor_id: ''
}
}Async Thunks:
fetchCartList— 获取购物车列表fetchSalesmanCartList— 获取业务员购物车addCart— 添加到购物车deleteCartItem— 删除购物车项updateCartItemNum— 更新数量updateCount— 更新购物车数量(角标)updateSalesmanCount— 更新业务员购物车数量
purchase Slice(内购状态)
js
initialState = {
isOpen: false, // 内购是否开启
tabbar: null, // 内购 tabbar 配置
purchase_share_info: {}, // 分享信息
invite_code: '', // 邀请码
validCart: [], invalidCart: [],
cartCount: 0,
hasValidIdentity: true, // 是否有有效身份
curEnterpriseId: null, // 当前企业 ID
curEnterpriseName: '', // 当前企业名称
priceDisplayConfig: {}, // 价格显示配置
isDiscountDescriptionEnabled: false,
discountDescription: '',
curDistributorId: null,
curEnterpriseLogo: '',
persist_purchase_share_info: {},
curActivityInfo: {},
isPasscodeLogin: false // 口令通道登录
}Async Thunks:fetchCartList、addCart、deleteCartItem、updateCartItemNum、updateCount
guide Slice(导购状态)
js
initialState = {
userInfo: null, // 导购员信息
storeInfo: null, // 店铺信息
cartCount: 0,
validCart: [], invalidCart: [],
list: [], cartIds: [],
fastbuy: null, // 快速购买商品
coupon: null, // 优惠券
freightCoupon: null, // 运费券
giftCoupon: null, // 礼品券
selection: [], // 选中项
count: '', // 数量
ads: null, // 广告
receiptType: '', // 收据类型
showBuyPanel: false, // 购买面板
goodsSkuInfo: null // SKU 信息
}sys Slice(系统配置)
js
initialState = {
initState: false, // 是否已初始化
colorPrimary: '#d42f29', // 主题色
colorMarketing: '#fba629', // 营销色
colorAccent: '#2e3030', // 强调色
rgb: '', // RGB 值
pointName: '积分', // 积分名称
pageTitle: '',
appName: '',
tabbar: { // TabBar 配置
config: { backgroundColor: '#fff', color: '#333', selectedColor: '#1f82e0' },
data: [/* home, category, cart, member */]
},
openStore: false,
openRecommend: 1,
openScanQrcode: 1,
openOfficialAccount: 1,
openWechatappLocation: 1,
priceSetting: { // 价格显示设置
cart_page, item_page, order_page
},
currency: { // 货币信息
symbol: '¥', currency: 'CNY'
}
}Reducers:
setSysConfig— 设置系统配置(含 RGB 颜色计算)updatePageTitle— 更新页面标题
community Slice(社区团购)
js
initialState = {
selectGoods: [], // 商品选品
selectCommunityZiti: null // 社区团购自提点
}注意:文件名是
community.js,但 slice name 是'select'。
member Slice
js
initialState = {
favs: {}, // { item_id: { item_id, fav_id } }
showAdv: true,
member: {}
}shop Slice
js
initialState = {
shopInfo: {}, // 店铺信息
salespersonInfo: {}, // 销售员信息
zitiShop: null, // 自提门店
showGuideConsultModal: false // 全局导购联系弹框
}dianwu Slice
js
initialState = { member: null } // 店务选中会员merchant Slice
js
initialState = {
merchantType: {}, // 商户类型
businessScope: {}, // 经营范围
bank: { name: '' } // 银行信息
}colors Slice
js
initialState = { current: {} }tabBar Slice
js
initialState = { current: null }Spx 与 Redux 的关系
职责划分
| 层 | 职责 | 存储 |
|---|---|---|
| Spx | 运行时缓存(Token、UV时间戳、钩子) | 内存 + Taro Storage |
| Redux | 页面级状态(购物车、用户信息、系统配置) | 内存 + redux-persist |
数据流向
用户登录成功
│
├─→ Spx.setAuthToken(token) # Token 存到 Spx
│ └─ 同步到 Taro Storage
│
├─→ Spx.getMemberInfo() # 调用 API 获取用户信息
│ └─→ dispatch(updateUserInfo(data)) # dispatch 到 Redux
│
└─→ dispatch(fetchCartList()) # Redux thunk 获取购物车
└─→ 组件从 useSelector 读取在组件中使用
jsx
import { useSelector, useDispatch } from 'react-redux'
import { updateUserInfo, clearUserInfo } from '@/store/slices/user'
import Spx from '@/spx'
function MyComponent() {
const userInfo = useSelector(state => state.user.userInfo)
const dispatch = useDispatch()
const handleLogout = () => {
Spx.logout() // 清 Token
dispatch(clearUserInfo()) // 清 Redux
}
return <View>{userInfo?.nick_name}</View>
}异步 Thunk 模式
项目中大量使用 createAsyncThunk 处理异步操作:
js
// 示例:购物车 thunk
export const fetchCartList = createAsyncThunk(
'cart/fetchCartList',
async (params, { dispatch }) => {
const data = await api.cart.getList(params)
return pickBy(data, {
validCart: 'valid_cart',
invalidCart: 'invalid_cart',
// ...
})
}
)
// 在 extraReducers 中处理
extraReducers: (builder) => {
builder
.addCase(fetchCartList.fulfilled, (state, action) => {
state.validCart = action.payload.validCart
state.invalidCart = action.payload.invalidCart
})
.addCase(fetchCartList.rejected, (state) => {
state.validCart = []
state.invalidCart = []
})
}在组件中使用 Redux
useSelector
js
// 获取单个字段
const cartCount = useSelector(state => state.cart.cartCount)
// 获取多个字段
const { userInfo, vipInfo, address } = useSelector(state => state.user)useDispatch
js
const dispatch = useDispatch()
dispatch(updateUserInfo(data))
dispatch(fetchCartList({ page: 1 })) // async thunk