@kordar-lib/reducers
v2.0.1
Published
预置基于 Redux Toolkit 的通用状态管理包,默认挂载 user / app / layout 三个 slice,内置类型化的 React Hooks,以及常用的业务工具方法。支持与 redux-persist 组合和动态注入业务 reducer。
Readme
@kordar-lib/reducers
预置基于 Redux Toolkit 的通用状态管理包,默认挂载 user / app / layout 三个 slice,内置类型化的 React Hooks,以及常用的业务工具方法。支持与 redux-persist 组合和动态注入业务 reducer。
特性
- 内置 store,默认挂载 user、app、layout
- 提供 useAppDispatch、useAppSelector 强类型 Hooks
- 暴露 RootState、AppDispatch 类型
- 提供 Admin、App 工具方法,简化常见调用
- 支持动态替换 reducer(loadNewReducer)
安装
npm install @kordar-lib/reducers @reduxjs/toolkit react-redux redux-persist快速开始
// store.ts(x)(应用侧)
import {persistReducer, persistStore} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import {combineReducers} from 'redux'
import {
store,
loadNewReducer,
adminReducer,
appReducer,
layoutReducer,
type RootState,
type AppDispatch,
useAppDispatch,
useAppSelector
} from '@kordar-lib/reducers'
const userPersist = persistReducer({ key: 'user', storage, whitelist: ['token', 'info'] }, adminReducer)
const appPersist = persistReducer({ key: 'app', storage, whitelist: ['sidebarCollapsed', 'locale', 'localeOptions', 'historyLocation', 'theme'] }, appReducer)
const layoutPersist = persistReducer({ key: 'layout', storage, whitelist: ['choices', 'tabs', 'choiceId'] }, layoutReducer)
loadNewReducer(combineReducers({
user: userPersist,
app: appPersist,
layout: layoutPersist
}))
export const persistor = persistStore(store)
export { store }在组件中使用
import React from 'react'
import {Provider} from 'react-redux'
import {store, useAppDispatch, useAppSelector, setLocale} from '@kordar-lib/reducers'
function LanguageSwitcher() {
const dispatch = useAppDispatch()
const locale = useAppSelector(s => s.app.locale)
return (
<button onClick={() => dispatch(setLocale({locale: 'en'}))}>
{locale}
</button>
)
}
export default function App() {
return (
<Provider store={store}>
<LanguageSwitcher />
</Provider>
)
}API 概览
Store 与类型
- store:预置的 Redux store 实例(已挂载 user/app/layout)
- loadNewReducer(reducer):动态替换根 reducer(常与 combineReducers + persistReducer 配合)
- RootState:返回值类型,含默认 slice 与扩展键
- AppDispatch:dispatch 类型
- useAppDispatch、useAppSelector:类型化的 React Hooks
Slice:user(./src/admin.ts)
- state: { token: string; info: Record<string, any>; permissions: Record<string, any>; version: number }
- actions: login(payload: {token?, info?, success?, error?}), logout(callback?), loadPermissions(data), incrementVersion()
Slice:app(./src/app.ts)
- state: { sidebarCollapsed: boolean; locale: string; localeOptions: Record<string,string>; theme?: string; historyLocation: {hash,search,pathname} }
- actions: setSidebarCollapsed(boolean), setLocale({locale,callback?}), setAppTheme(string), setLocaleOptions(record), saveHistoryLocation({hash,search,pathname})
Slice:layout(./src/layout.ts)
- state: { choices: (number|string)[]; tabs: (number|string)[]; choiceId: number|string }
- actions: sidebarChoice({id,choices|null}), justSaveChoice({choiceId}), saveTabs({newTabs?}), saveChoice({choices}), removeTab({id}), clearLayoutData()
工具方法(./src/util.ts)
- Admin
- isGuest(): boolean
- isSuper(): boolean
- token(): string
- info(): Record<string, any>
- logout(callback?: () => void): void
- permissions(): Record<string, any>
- loadPermissions(data: any[] | Record<string, any>): void
- incrementVersion(): void
- can(...names: string[]): boolean
- orCan(...names: string[]): boolean
- clear(): void // 清理 layout 并重置历史位置
- App
- locale(): string
- loadLanguageOptions(record: Record<string,string>): void
- saveHistoryLocation(location: Location): void
动态注入业务 reducer
loadNewReducer 会整体替换根 reducer。注入业务 slice 时,务必把 user/app/layout 也一起合并进新的根 reducer(否则对应状态会丢失)。
import {combineReducers} from 'redux'
import {persistReducer} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import {loadNewReducer, adminReducer, appReducer, layoutReducer} from '@kordar-lib/reducers'
import someFeature from './someFeature'
const userPersist = persistReducer({ key: 'user', storage, whitelist: ['token', 'info'] }, adminReducer)
const appPersist = persistReducer({ key: 'app', storage, whitelist: ['sidebarCollapsed', 'locale', 'localeOptions', 'historyLocation', 'theme'] }, appReducer)
const layoutPersist = persistReducer({ key: 'layout', storage, whitelist: ['choices', 'tabs', 'choiceId'] }, layoutReducer)
const nextRoot = combineReducers({
user: userPersist,
app: appPersist,
layout: layoutPersist,
someFeature
})
loadNewReducer(nextRoot)自定义 Slice 教程(创建并挂载)
下面以“计数器”功能为例,演示如何新建业务 slice,并在应用侧挂载到根 reducer。
步骤一:创建业务 slice(TypeScript)
// src/store/myCounter.ts
import {createSlice, PayloadAction} from '@reduxjs/toolkit'
export interface MyCounterState {
value: number
}
const initialState: MyCounterState = { value: 0 }
const myCounterSlice = createSlice({
name: 'myCounter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
add: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
})
export const { increment, add } = myCounterSlice.actions
export default myCounterSlice.reducer步骤二(可选):配置持久化
// src/store/index.ts(x)
import {persistReducer, persistStore} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import myCounterReducer from './myCounter'
const myCounterPersist = persistReducer(
{ key: 'myCounter', storage, whitelist: ['value'] },
myCounterReducer
)步骤三:与既有切片合并并注入
import {combineReducers} from 'redux'
import {persistReducer} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import {
loadNewReducer,
adminReducer, appReducer, layoutReducer
} from '@kordar-lib/reducers'
import myCounterReducer from './myCounter'
const userPersist = persistReducer({ key: 'user', storage, whitelist: ['token','info'] }, adminReducer)
const appPersist = persistReducer({ key: 'app', storage, whitelist: ['sidebarCollapsed','locale','localeOptions','historyLocation','theme'] }, appReducer)
const layoutPersist = persistReducer({ key: 'layout', storage, whitelist: ['choices','tabs','choiceId'] }, layoutReducer)
const root = combineReducers({
user: userPersist,
app: appPersist,
layout: layoutPersist,
myCounter: myCounterReducer // 新增挂载
})
loadNewReducer(root)步骤四:组件中使用
import React from 'react'
import {useAppDispatch, useAppSelector} from '@kordar-lib/reducers'
import {increment, add} from '@/store/myCounter'
export default function Counter() {
const dispatch = useAppDispatch()
const value = useAppSelector(state => state.myCounter.value)
return (
<div>
<span>{value}</span>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(add(5))}>+5</button>
</div>
)
}提示
- 如果根 reducer 由
loadNewReducer动态替换,确保每次替换时都包含 user/app/layout 以及你的自定义切片。 - 需要持久化的切片用
persistReducer包裹后再参与combineReducers。 - 在 TS 项目中,选择器可通过
useAppSelector((s) => s.myCounter.value as number)或为RootState编写模块扩展来获得更精准类型。
持久化集成(redux-persist)
- 推荐在应用侧以 persistReducer 包裹各切片
- 再通过 combineReducers + loadNewReducer 注入
- 参考示例:
- 模板 TS 示例:../../templates/ant-design-tpl/examples/store.ts
- 模板 JS 示例:../../projects/template/src/store.js
构建与类型(Monorepo:npm)
在仓库根目录执行:
npm install
npm run -w @kordar-lib/reducers build需要单独做 TypeScript 类型检查时:
npm exec -w @kordar-lib/reducers -- tsc -p tsconfig.json --noEmit文件索引
- 入口导出:./src/index.ts
- 基础 store:./src/store.ts
- 切片:
- user:./src/admin.ts
- app:./src/app.ts
- layout:./src/layout.ts
