Skip to content

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/PERSIST action(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系统配置每次启动从服务端拉取

注意:community Slice 的 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 ThunksfetchCartListaddCartdeleteCartItemupdateCartItemNumupdateCount

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