@game-flux/core
v1.0.1
Published
用于游戏开发的轻量级状态管理库
Maintainers
Readme
GameFlux
为游戏而生
GameFlux 是专为游戏开发设计的轻量级状态管理库,提供简洁的API和出色的跨平台兼容性。
为什么选择 GameFlux?
适用场景
- 游戏开发状态管理 - GameFlux 专为游戏优化,支持 ECS
- 跨小程序/多平台 - 完美兼容微信、抖音、H5、原生
- 简单易用 + 完整 TypeScript - 完整的类型推导和提示
- 时间旅行调试游戏 - 内置完整 DevTools
- 轻量级设计 - 零依赖,体积小巧
- 不可变更新 + 简洁语法 - 内置 Immer-like 实现,写法简洁
特性
核心特性
- 不可变更新 - 内置轻量级 Immer 实现,写法像可变操作
- 完整TypeScript支持 - 100% TypeScript编写,完美的类型推导
- 模块化设计 - 真正的模块隔离,避免状态混乱
- 零依赖 - 无任何第三方依赖(除序列化库devalue)
性能优化
- Getter缓存 - 智能缓存计算结果,避免重复计算
- 选择性订阅 - 只监听关心的状态变化
- 批量更新 - 支持批量提交mutations
- 懒加载模块 - 按需加载模块,减少初始化开销
跨平台兼容
- H5 - 完美支持浏览器环境
- 微信小程序 - 支持微信、抖音等小程序
- 原生App - 支持 iOS/Android 原生环境
- Cocos Creator - 深度优化 Cocos Creator 集成
开发工具
- 时间旅行调试 - 撤销/重做/状态快照
- 日志插件 - 自动记录所有 mutation 和 action
- 持久化插件 - 跨平台本地存储方案
- 插件系统 - 轻松扩展功能
安装
npm install @game-flux/core
# 或者使用 yarn
yarn add @game-flux/core
# 或者使用 pnpm
pnpm add @game-flux/core快速开始
1. 定义模块
import { defineModule } from '@game-flux/core';
// 定义玩家模块
const playerModule = defineModule({
state: {
coins: 0,
level: 1,
exp: 0
},
mutations: {
// 添加金币
addCoins(state, amount: number) {
state.coins += amount; // 像可变操作一样直接修改
},
// 升级
levelUp(state) {
state.level += 1;
state.exp = 0;
},
// 添加经验
addExp(state, amount: number) {
state.exp += amount;
}
},
getters: {
// 是否富有
isRich(state) {
return state.coins > 1000;
},
// 经验百分比
expPercentage(state) {
const required = state.level * 100;
return Math.floor((state.exp / required) * 100);
}
},
actions: {
// 购买物品
async buyItem({ state, commit }, { price }: { price: number }) {
if (state.coins >= price) {
commit('addCoins', -price);
return true;
}
return false;
}
}
});2. 创建 Store
import { createStore } from '@game-flux/core';
const store = createStore({
state: {
loading: false
},
mutations: {
setLoading(state, loading: boolean) {
state.loading = loading;
}
},
modules: {
player: playerModule
}
});3. 使用 Store
// 读取状态
console.log(store.state.player.coins); // 0
// 提交 mutation
store.commit('player/addCoins', 100);
console.log(store.state.player.coins); // 100
// 使用 getter
console.log(store.getters['player/isRich']); // false
// 分发 action
await store.dispatch('player/buyItem', { price: 50 });
console.log(store.state.player.coins); // 50
// 订阅变化
store.subscribe((mutation, state) => {
console.log('Mutation:', mutation.type);
console.log('New state:', state);
});核心概念
State(状态)
State 是应用的唯一数据源,采用单一状态树结构。
interface RootState {
loading: boolean;
player: {
coins: number;
level: number;
};
game: {
score: number;
time: number;
};
}Mutations(突变)
Mutation 是修改 state 的唯一方式,必须是同步函数。
mutations: {
// 使用 payload 参数
addCoins(state, amount: number) {
state.coins += amount;
},
// 使用对象参数
updatePlayer(state, { coins, level }: Partial<PlayerState>) {
if (coins !== undefined) state.coins = coins;
if (level !== undefined) state.level = level;
}
}Actions(动作)
Action 用于处理异步操作,可以包含任意异步逻辑。
actions: {
async loadPlayerData({ commit }) {
const data = await fetchPlayerData();
commit('updatePlayer', data);
},
async saveProgress({ state }) {
await saveToServer(state);
}
}Getters(计算属性)
Getter 用于从 state 派生出一些状态,会自动缓存。
getters: {
// 简单 getter
totalScore(state) {
return state.kills * 100 + state.combos * 50;
},
// 依赖其他 getter
rank(state, getters) {
const score = getters.totalScore;
return score > 10000 ? 'S' : 'A';
}
}Modules(模块)
模块让你可以将 store 分割成多个模块,每个模块拥有自己的 state、mutation、action、getter。
const store = createStore({
modules: {
player: playerModule,
game: gameModule,
ui: uiModule
}
});
// 访问模块状态
store.state.player.coins;
// 提交模块 mutation
store.commit('player/addCoins', 100);
// 分发模块 action
store.dispatch('player/loadData');
// 访问模块 getter
store.getters['player/isRich'];高级特性
批量更新
减少重复渲染,提升性能:
// 批量提交多个 mutation,只触发一次订阅通知
store.batch(() => {
store.commit('player/addCoins', 100);
store.commit('player/addExp', 50);
store.commit('player/levelUp');
});选择性订阅(watch)
只监听关心的状态变化:
// 只监听玩家金币变化
const unwatch = store.watch(
state => state.player.coins,
(newCoins, oldCoins) => {
console.log(`金币从 ${oldCoins} 变为 ${newCoins}`);
}
);
// 取消监听
unwatch();
// 监听多个字段
store.watch(
state => ({ coins: state.player.coins, level: state.player.level }),
(newValue, oldValue) => {
console.log('玩家数据变化:', newValue);
},
{ deep: true } // 深度监听
);结构共享优化
未修改的部分自动保持引用,提升性能:
const oldState = store.state;
store.commit('setLoading', true); // 只修改 loading
const newState = store.state;
// 未修改的模块保持原引用
oldState.player === newState.player // true!
// 可以用 === 快速判断组件是否需要更新
if (oldState.player !== newState.player) {
updatePlayerUI();
}严格模式
严格模式可以帮助你在开发阶段检测不规范的状态修改操作。
const store = createStore({
state: { count: 0 },
mutations: {
increment(state) {
state.count++; // 正确:在 mutation 中修改
}
},
strict: true // 启用严格模式
});
// 正确:通过 mutation 修改状态
store.commit('increment'); // OK
// 错误:直接修改 state(开发环境会警告)
store.state.count++; // Warning: 直接修改 state 可能导致状态不一致严格模式的作用:
- 检测在 mutation 之外对 state 根级属性的直接修改
- 仅在开发环境生效,不影响生产环境性能
- 通过 Proxy 监听状态对象的修改
- 帮助养成良好的状态管理习惯
严格模式的限制:
严格模式只能检测根级属性的直接修改,无法检测深层嵌套属性的修改:
const store = createStore({
state: {
loading: false,
user: { name: 'Alice', age: 20 }
},
strict: true
});
// 会被检测到(根级属性)
store.state.loading = true; // Warning!
// 无法检测到(深层属性)
store.state.user.name = 'Bob'; // 不会警告,但仍然是错误的做法
store.state.user.age = 25; // 不会警告,但仍然是错误的做法推荐方案:
- 配合 TypeScript 的
readonly类型确保类型安全 - 使用 ESLint 规则禁止直接修改 state
- 团队代码审查时注意状态修改规范
- 最重要:所有状态修改都通过
commit提交 mutation
注意事项:
- 严格模式仅用于开发调试,生产环境会自动禁用
- 启用严格模式会有轻微的性能开销(仅开发环境)
- 所有状态修改必须通过
commit提交 mutation - 在 action 中修改 state 也会触发警告,应该通过
commit提交 mutation
使用场景:
- 大型项目中确保团队成员遵循状态管理规范
- 学习阶段帮助理解单向数据流的概念
- 调试状态不一致问题时快速定位错误修改
插件系统
持久化插件
import { createStore, persistPlugin } from '@game-flux/core';
const store = createStore({
modules: { player },
plugins: [
persistPlugin({
key: 'my-game-state',
paths: ['player.coins', 'player.level'],
storage: 'auto', // 自动检测环境
debounce: 1000 // 防抖1秒
})
]
});日志插件
import { loggerPlugin } from '@game-flux/core';
const store = createStore({
modules: { player },
plugins: [
loggerPlugin({
collapsed: true,
filter(mutation) {
// 过滤掉频繁的 mutation
return !mutation.type.includes('updateTimer');
}
})
]
});时间旅行调试
import { timeTravelPlugin } from '@game-flux/core';
const store = createStore({
modules: { player },
plugins: [
timeTravelPlugin({
maxHistory: 50
})
]
});
// 使用时间旅行
store.devtools.undo(); // 撤销
store.devtools.redo(); // 重做
store.devtools.jumpTo(10); // 跳转到第10个状态
const snapshot = store.devtools.exportSnapshot(); // 导出快照最佳实践
1. 模块化组织
GameFlux 采用扁平化模块设计,推荐使用命名约定表达层级关系:
// 推荐:使用点号表达层级
const store = createStore({
modules: {
'player': playerModule,
'player.inventory': inventoryModule, // 玩家背包
'player.skills': skillsModule, // 玩家技能
'game': gameModule,
'game.battle': battleModule, // 战斗模块
'ui': uiModule
}
});
// 使用时仍然是扁平的
store.commit('player.inventory/addItem', item);
store.getters['player.skills/learned'];
// 不支持:嵌套模块
// modules: {
// player: {
// modules: { inventory: {...} } // 不支持嵌套模块
// }
// }文件组织:
src/
store/
index.ts # Store 入口
modules/
player.ts # 玩家模块
player.inventory.ts # 玩家背包模块
player.skills.ts # 玩家技能模块
game.ts # 游戏模块
ui.ts # UI模块2. 类型安全
// 定义类型
interface PlayerState {
coins: number;
level: number;
}
// 使用类型
const playerModule = defineModule<PlayerState>({
state: {
coins: 0,
level: 1
},
// TypeScript 会自动推导参数类型
mutations: {
addCoins(state, amount: number) {
state.coins += amount;
}
}
});3. 避免在 Getter 中进行重计算
// 不推荐 - 每次都创建新数组
getters: {
sortedItems(state) {
return [...state.items].sort();
}
}
// 推荐 - 使用缓存或在 mutation 中排序
mutations: {
addItem(state, item) {
state.items.push(item);
state.items.sort(); // 在修改时排序
}
}4. 异步操作统一在 Action 中处理
// 不推荐 - 在组件中处理异步
async function onButtonClick() {
const data = await api.fetchData();
store.commit('setData', data);
}
// 推荐 - 在 action 中处理
actions: {
async loadData({ commit }) {
const data = await api.fetchData();
commit('setData', data);
}
}
// 组件中
async function onButtonClick() {
await store.dispatch('loadData');
}Cocos Creator 集成
在组件中使用
import { Component } from 'cc';
import { store } from './store';
export class PlayerComponent extends Component {
private unsubscribe?: () => void;
onLoad() {
// 订阅状态变化
this.unsubscribe = store.watch(
state => state.player.coins,
(coins) => {
this.updateCoinsDisplay(coins);
}
);
}
onDestroy() {
// 组件销毁时取消订阅
this.unsubscribe?.();
}
onButtonClick() {
// 提交 mutation
store.commit('player/addCoins', 100);
}
private updateCoinsDisplay(coins: number) {
// 更新 UI
}
}在 ECS 系统中使用
import { ecs } from '@flux/core';
import { store } from './store';
export class GameSystem extends ecs.System {
async run() {
// 分发 action
await store.dispatch('game/loadLevel', { levelId: 1 });
// 读取状态
const score = store.state.game.score;
}
}API 文档
API 快速参考
核心 APIs
| API | 说明 | 返回值 |
|-----|------|--------|
| createStore(options) | 创建 store 实例 | Store<S> |
| defineModule(options) | 定义状态模块 | ModuleOptions<S> |
| produce(base, recipe) | 不可变更新助手 | T |
Store 实例方法
| 方法 | 说明 | 返回值 |
|------|------|--------|
| store.state | 获取当前状态树(只读) | S |
| store.getters | 获取所有 getter 结果 | Record<string, any> |
| store.commit(type, payload) | 同步提交 mutation | void |
| store.dispatch(type, payload) | 分发 action(异步) | Promise<any> |
| store.subscribe(subscriber) | 订阅所有 mutations | Unsubscriber |
| store.watch(selector, callback, options) | 监听特定状态变化 | Unsubscriber |
| store.batch(fn) | 批量提交 mutations | void |
| store.replaceState(state) | 替换整个状态树 | void |
插件 APIs
| 插件 | 说明 |
|------|------|
| persistPlugin(options) | 状态持久化 |
| loggerPlugin(options) | 日志记录 |
| timeTravelPlugin(options) | 时间旅行调试 |
| createStorageAdapter(type) | 创建存储适配器 |
createStore(options)
创建一个 GameFlux store 实例。
类型签名
function createStore<S = any>(options: StoreOptions<S>): Store<S>参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| options.state | Object | 否 | {} | 根级状态对象 |
| options.mutations | Object | 否 | {} | 根级 mutation 函数映射 |
| options.actions | Object | 否 | {} | 根级 action 函数映射 |
| options.getters | Object | 否 | {} | 根级 getter 函数映射 |
| options.modules | Object | 否 | {} | 子模块映射 |
| options.plugins | Plugin[] | 否 | [] | 插件数组 |
| options.strict | boolean | 否 | false | 严格模式,禁止在 mutation 外修改 state |
| options.devMode | boolean | 否 | false | 开发模式,开启更多警告 |
返回值
| 类型 | 描述 |
|------|------|
| Store<S> | store 实例,包含 state、getters、commit、dispatch 等方法 |
示例
const store = createStore({
state: { loading: false },
mutations: {
setLoading(state, loading: boolean) {
state.loading = loading;
}
},
modules: {
player: playerModule
},
plugins: [persistPlugin(), loggerPlugin()]
});相关 API
defineModule()- 定义模块store.commit()- 提交 mutation
defineModule(options)
定义一个状态模块,用于组织和隔离状态逻辑。
类型签名
function defineModule<S extends ModuleState>(
options: ModuleOptions<S>
): ModuleOptions<S>参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| options.state | Object | 是 | - | 模块状态对象 |
| options.mutations | Object | 否 | {} | 模块 mutations |
| options.actions | Object | 否 | {} | 模块 actions |
| options.getters | Object | 否 | {} | 模块 getters |
返回值
| 类型 | 描述 |
|------|------|
| ModuleOptions<S> | 模块配置对象,传递给 createStore 的 modules 选项 |
示例
const playerModule = defineModule({
state: { coins: 0, level: 1 },
mutations: {
addCoins(state, amount: number) {
state.coins += amount; // 直接修改,内部使用 Immer
}
},
getters: {
isRich(state) {
return state.coins > 1000;
}
},
actions: {
async loadData({ commit }) {
const data = await fetchPlayerData();
commit('updatePlayer', data);
}
}
});注意事项
- 主要用于 TypeScript 类型推导,运行时等同于直接返回对象
- 每个模块的 state、mutations、getters 都是隔离的
相关 API
createStore()- 创建 store
store.state
获取当前状态树(只读)。
类型
readonly state: S示例
console.log(store.state.loading); // 访问根级状态
console.log(store.state.player.coins); // 访问模块状态注意事项
- 不要直接修改 state,必须通过 mutation
- TypeScript 会将 state 标记为只读
相关 API
store.commit()- 修改状态
store.getters
获取所有 getter 的计算结果。
类型
readonly getters: Record<string, any>示例
// 访问根级 getter
const ready = store.getters.isReady;
// 访问模块 getter
const isRich = store.getters['player/isRich'];注意事项
- Getter 结果会自动缓存,只有依赖的 state 变化时才重新计算
- 访问模块 getter 使用
'moduleName/getterName'格式
相关 API
defineModule()- 定义 getters
store.commit(type, payload)
同步提交一个 mutation 来修改状态。这是修改 state 的唯一方式。
类型签名
commit<P = any>(type: string, payload?: P): void参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| type | string | 是 | Mutation 类型,支持命名空间(如 'player/addCoins')|
| payload | any | 否 | 传递给 mutation 的数据 |
返回值
无返回值(void)
示例
// 根级 mutation
store.commit('setLoading', true);
// 模块 mutation
store.commit('player/addCoins', 100);
// 对象参数
store.commit('player/update', { coins: 100, level: 2 });注意事项
- Mutation 必须是同步函数
- 不要在 mutation 中调用异步操作(如 API 请求、setTimeout)
- 异步操作请使用
store.dispatch() - 每次 commit 都会触发所有订阅者
相关 API
store.dispatch()- 分发 actionstore.subscribe()- 订阅 mutationsstore.batch()- 批量提交
store.dispatch(type, payload)
分发一个 action,可以包含异步操作。
类型签名
dispatch<P = any>(type: string, payload?: P): Promise<any>参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| type | string | 是 | Action 类型,支持命名空间 |
| payload | any | 否 | 传递给 action 的数据 |
返回值
| 类型 | 描述 |
|------|------|
| Promise<any> | action 函数的返回值 |
示例
// 异步 action
await store.dispatch('player/loadData');
// 带参数和返回值
const result = await store.dispatch('player/buyItem', {
id: 123,
price: 100
});
if (result.success) {
console.log('购买成功');
}使用场景
- API 请求
- 异步数据处理
- 复杂的业务逻辑
- 需要提交多个 mutations
注意事项
- Action 内部通过
commit提交 mutation - 可以返回 Promise 供调用者等待
相关 API
store.commit()- 提交 mutation
store.subscribe(subscriber)
订阅所有 mutation 的变化。
类型签名
subscribe(
subscriber: (mutation: MutationInfo, state: S, prevState: S) => void
): Unsubscriber参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| subscriber | Function | 是 | 订阅函数,在每次 mutation 后调用 |
订阅函数参数
| 参数 | 类型 | 描述 |
|------|------|------|
| mutation | MutationInfo | Mutation 信息,包含 type、payload、timestamp |
| state | S | 新状态 |
| prevState | S | 旧状态 |
返回值
| 类型 | 描述 |
|------|------|
| Unsubscriber | 取消订阅的函数 |
示例
const unsubscribe = store.subscribe((mutation, state, prevState) => {
console.log('Mutation:', mutation.type);
console.log('Payload:', mutation.payload);
console.log('State changed:', prevState !== state);
});
// 取消订阅
unsubscribe();使用场景
- 持久化插件(监听所有变化并保存)
- 日志记录
- 调试工具
- 状态同步到其他系统
相关 API
store.watch()- 选择性订阅store.commit()- 提交 mutation
store.watch(selector, callback, options)
监听特定状态的变化,只在关心的数据变化时触发(选择性订阅)。
类型签名
watch<T>(
selector: (state: S) => T,
callback: (newValue: T, oldValue: T) => void,
options?: WatchOptions
): Unsubscriber参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| selector | Function | 是 | - | 选择器函数,从 state 中提取要监听的值 |
| callback | Function | 是 | - | 回调函数,在值变化时调用 |
| options.immediate | boolean | 否 | false | 是否立即执行一次 |
| options.deep | boolean | 否 | false | 深度监听(对象/数组) |
返回值
| 类型 | 描述 |
|------|------|
| Unsubscriber | 取消监听的函数 |
示例
// 监听单个字段
const unwatch = store.watch(
state => state.player.coins,
(newCoins, oldCoins) => {
console.log(`金币从 ${oldCoins} 变为 ${newCoins}`);
}
);
// 监听多个字段
store.watch(
state => ({
coins: state.player.coins,
level: state.player.level
}),
(newValue, oldValue) => {
updateUI(newValue);
},
{ immediate: true, deep: true }
);
// 取消监听
unwatch();使用场景
- UI 组件监听特定数据
- 性能敏感场景(避免监听所有变化)
- 条件触发逻辑
性能对比
| 方法 | 触发频率 | 适用场景 |
|------|----------|----------|
| subscribe | 每次 mutation | 日志、持久化 |
| watch | 只在关心的数据变化 | UI 更新、条件逻辑 |
注意事项
- 相比
subscribe,性能更好 deep: true会进行深度比较,有性能开销
相关 API
store.subscribe()- 订阅所有 mutations
store.batch(fn)
批量提交多个 mutation,只触发一次订阅通知。
类型签名
batch(fn: () => void): void参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| fn | Function | 是 | 要执行的批量操作函数 |
返回值
无返回值(void)
示例
store.batch(() => {
store.commit('player/addCoins', 100);
store.commit('player/addExp', 50);
store.commit('player/levelUp');
});
// 所有订阅者只会收到一次通知性能对比
| 方式 | 订阅触发次数 | 渲染次数 | |------|--------------|----------| | 逐个 commit | 3次 | 3次 | | batch | 1次 | 1次 |
使用场景
- 游戏中一次操作涉及多个状态变化
- 批量数据初始化
- 减少UI重复渲染
注意事项
- 显著减少重复渲染,提升性能
- batch 内的所有 mutation 仍然是原子性的
相关 API
store.commit()- 提交 mutation
store.replaceState(state)
替换整个状态树(用于时间旅行、状态恢复)。
类型签名
replaceState(state: S): void参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| state | S | 是 | 新的状态对象 |
返回值
无返回值(void)
示例
// 恢复保存的状态
const savedState = localStorage.getItem('gameState');
if (savedState) {
store.replaceState(JSON.parse(savedState));
}
// 时间旅行
store.devtools.jumpTo(5); // 内部调用 replaceState使用场景
- 状态恢复(从本地存储)
- 时间旅行调试
- 服务器同步状态
注意事项
- 会触发所有订阅者
- 谨慎使用,可能导致状态不一致
- 建议只在初始化或调试时使用
相关 API
timeTravelPlugin()- 时间旅行插件
produce(base, recipe)
不可变更新助手函数(Immer-like),让你可以用"可变"的方式写不可变更新。
类型签名
function produce<T>(
base: T,
recipe: (draft: T) => void
): T参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| base | T | 是 | 原始对象(不会被修改) |
| recipe | Function | 是 | 修改函数,接收 draft 并"直接修改" |
返回值
| 类型 | 描述 |
|------|------|
| T | 新的不可变对象(如果有修改)或原对象(如果无修改,结构共享) |
示例
import { produce } from '@game-flux/core';
const currentState = {
user: { name: 'Bob', age: 20 },
items: [1, 2, 3],
config: { theme: 'dark' }
};
const nextState = produce(currentState, draft => {
draft.user.name = 'Alice'; // 直接修改
draft.items.push(4); // 数组操作
});
// 不可变性
console.log(currentState.user.name); // 'Bob'(原对象不变)
console.log(nextState.user.name); // 'Alice'(新对象)
// 结构共享
console.log(currentState.config === nextState.config); // true(未修改的部分共享引用)优势
| 特性 | 传统方式 | produce 方式 |
|------|----------|--------------|
| 语法 | { ...state, user: { ...state.user, name: 'Alice' } } | draft.user.name = 'Alice' |
| 可读性 | 一般 | 优秀 |
| 嵌套更新 | 复杂 | 简单 |
| 结构共享 | 手动 | 自动 |
注意事项
- GameFlux 内部自动使用
produce,mutation 中直接修改即可 - 支持结构共享,未修改的部分保持原引用
- 如果没有任何修改,返回原对象(性能优化)
- 不支持 Map、Set(使用普通对象代替)
相关 API
store.commit()- 内部使用 produce
插件 API 文档
persistPlugin(options)
状态持久化插件,自动保存和恢复状态。
类型签名
function persistPlugin<S = any>(options?: PersistOptions): Plugin<S>参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| options.key | string | 否 | 'game-flux-state' | 存储键名 |
| options.storage | StorageAdapter | 否 | 自动检测 | 存储适配器 |
| options.paths | string[] | 否 | undefined | 要持久化的路径,undefined 表示全部 |
| options.excludePaths | string[] | 否 | [] | 要排除的路径 |
| options.debounce | number | 否 | 1000 | 防抖延迟时间(毫秒),避免频繁保存 |
| options.serializer | 'devalue' \| 'json' \| Serializer | 否 | 'devalue' | 序列化器:支持 Date/Map/Set/RegExp/循环引用 |
返回值
| 类型 | 描述 |
|------|------|
| Plugin<S> | 插件实例 |
示例
import { createStore, persistPlugin, createStorageAdapter } from '@game-flux/core';
const store = createStore({
state: { player: { coins: 0 }, ui: { theme: 'dark' } },
plugins: [
persistPlugin({
key: 'my-game',
paths: ['player'], // 只保存 player
excludePaths: ['ui.theme'], // 排除 ui.theme
debounce: 2000 // 状态稳定2秒后保存
})
]
});
// 使用自定义存储
const customStorage = createStorageAdapter('cocos');
const store2 = createStore({
plugins: [persistPlugin({ storage: customStorage })]
});
// 使用 devalue 序列化器(默认,支持特殊类型)
const store3 = createStore({
state: {
loginTime: new Date(),
settings: new Map([['theme', 'dark']]),
tags: new Set(['vip', 'premium'])
},
plugins: [persistPlugin()] // 默认使用 devalue
});
// 使用 JSON 序列化器(更快,但不支持特殊类型)
const store4 = createStore({
state: { count: 0, name: 'game' },
plugins: [persistPlugin({ serializer: 'json' })]
});序列化器对比
| 序列化器 | 性能 | 支持类型 | 适用场景 | |---------|------|---------|---------| | devalue(默认) | 序列化约 5x 慢,反序列化约 2x 慢 | Date, Map, Set, RegExp, 循环引用等 | 状态包含特殊类型 | | json | 最快 | 基本类型、普通对象/数组 | 纯数据状态,追求性能 |
支持的特殊类型(devalue)
const store = createStore({
state: {
// Date 对象
loginTime: new Date('2024-01-01'),
// Map 对象
settings: new Map([['theme', 'dark'], ['lang', 'zh']]),
// Set 对象
tags: new Set(['vip', 'new', 'active']),
// RegExp 对象
pattern: /[0-9]+/gi,
// 循环引用
circular: { name: 'test' }
},
plugins: [persistPlugin()] // 自动处理这些类型
});
// 循环引用示例
store.state.circular.self = store.state.circular;支持的平台
| 平台 | 存储方式 | 适配器类型 |
|------|----------|-----------|
| Web/H5 | localStorage | 'h5' |
| 微信小程序 | wx.getStorageSync | 'wechat' |
| Cocos Creator | sys.localStorage | 'cocos' |
| 自定义 | 实现 StorageAdapter 接口 | 自定义对象 |
注意事项
- 首次创建 store 时会自动从存储恢复状态
- 使用
debounce选项避免频繁写入影响性能 - 大型状态树建议使用
paths只持久化必要部分
相关 API
createStorageAdapter()- 创建存储适配器
loggerPlugin(options)
日志记录插件,用于调试和监控。
类型签名
function loggerPlugin<S = any>(options?: LoggerOptions): Plugin<S>参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| options.collapsed | boolean | 否 | true | 是否折叠日志组 |
| options.logMutations | boolean | 否 | true | 是否记录 mutations |
| options.logActions | boolean | 否 | true | 是否记录 actions |
| options.logState | boolean | 否 | true | 是否记录状态变化 |
| options.filter | (mutation) => boolean | 否 | undefined | 过滤函数,返回 true 才记录 |
返回值
| 类型 | 描述 |
|------|------|
| Plugin<S> | 插件实例 |
示例
import { createStore, loggerPlugin } from '@game-flux/core';
const store = createStore({
plugins: [
loggerPlugin({
collapsed: false, // 展开日志
logActions: true,
filter: (mutation) => {
// 只记录 player 模块的变化
return mutation.type.startsWith('player/');
}
})
]
});日志输出示例
GameFlux Mutation @ 12:34:56.789
├─ Type: player/addCoins
├─ Payload: 100
├─ Prev State: { player: { coins: 0 } }
└─ Next State: { player: { coins: 100 } }注意事项
- 仅在开发环境使用,生产环境建议禁用
- 使用
filter减少日志噪音 - 大量日志可能影响性能
相关 API
timeTravelPlugin()- 时间旅行插件
timeTravelPlugin(options)
时间旅行调试插件,支持撤销/重做、状态快照。
类型签名
function timeTravelPlugin<S = any>(options?: TimeTravelOptions): Plugin<S>参数
| 参数 | 类型 | 必填 | 默认值 | 描述 |
|------|------|:----:|--------|------|
| options.maxHistory | number | 否 | 50 | 最大历史记录数 |
| options.enabled | boolean | 否 | true | 是否启用 |
返回值
| 类型 | 描述 |
|------|------|
| Plugin<S> | 插件实例,包含时间旅行 API |
示例
import { createStore, timeTravelPlugin } from '@game-flux/core';
const timeTravel = timeTravelPlugin({ maxHistory: 100 });
const store = createStore({
state: { step: 0 },
mutations: {
increment(state) { state.step++; }
},
plugins: [timeTravel]
});
// 进行一些操作
store.commit('increment'); // step = 1
store.commit('increment'); // step = 2
store.commit('increment'); // step = 3
// 时间旅行
timeTravel.undo(); // step = 2
timeTravel.undo(); // step = 1
timeTravel.redo(); // step = 2
timeTravel.jumpTo(0); // step = 0
// 获取历史记录
const history = timeTravel.getHistory();
console.log(history.length); // 4
// 导出/导入快照
const snapshot = timeTravel.exportSnapshot();
localStorage.setItem('snapshot', JSON.stringify(snapshot));
const saved = JSON.parse(localStorage.getItem('snapshot'));
timeTravel.importSnapshot(saved);时间旅行 API
| 方法 | 描述 | 返回值 |
|------|------|--------|
| undo() | 撤销到上一个状态 | void |
| redo() | 重做到下一个状态 | void |
| jumpTo(index) | 跳转到指定历史索引 | void |
| getHistory() | 获取历史记录 | HistoryEntry[] |
| clearHistory() | 清空历史记录 | void |
| exportSnapshot() | 导出当前快照 | Snapshot |
| importSnapshot(snapshot) | 导入快照 | void |
使用场景
- 游戏回放系统
- 调试和测试
- 教学演示
- 错误恢复
注意事项
- 每次 mutation 都会保存到历史记录
- 历史记录超过
maxHistory时会自动清理最早的记录 - 仅开发环境使用,生产环境会消耗大量内存
相关 API
store.replaceState()- 替换状态loggerPlugin()- 日志插件
createStorageAdapter(type)
创建跨平台存储适配器。
类型签名
function createStorageAdapter(
type: 'h5' | 'wechat' | 'cocos' | StorageAdapter
): StorageAdapter参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|:----:|------|
| type | string \| StorageAdapter | 是 | 平台类型或自定义适配器 |
返回值
| 类型 | 描述 |
|------|------|
| StorageAdapter | 存储适配器实例 |
示例
import { createStorageAdapter } from '@game-flux/core';
// 预设适配器
const h5Storage = createStorageAdapter('h5');
const wechatStorage = createStorageAdapter('wechat');
const cocosStorage = createStorageAdapter('cocos');
// 自定义适配器
const customStorage = createStorageAdapter({
getItem(key: string): string | null {
return myCustomStorage.get(key);
},
setItem(key: string, value: string): void {
myCustomStorage.set(key, value);
},
removeItem(key: string): void {
myCustomStorage.delete(key);
}
});StorageAdapter 接口
interface StorageAdapter {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}平台适配器实现
| 平台 | getItem | setItem | removeItem |
|------|-----------|-----------|--------------|
| H5 | localStorage.getItem | localStorage.setItem | localStorage.removeItem |
| 微信小程序 | wx.getStorageSync | wx.setStorageSync | wx.removeStorageSync |
| Cocos Creator | sys.localStorage.getItem | sys.localStorage.setItem | sys.localStorage.removeItem |
注意事项
- 自动检测当前平台并使用合适的适配器
- 所有值都会自动进行 JSON 序列化/反序列化
- 某些平台有存储大小限制(如微信小程序 10MB)
相关 API
persistPlugin()- 持久化插件
TypeScript 类型系统
GameFlux 提供完整的类型推导,无需手动标注类型。
自动类型推导
// state 类型自动推导
const playerModule = defineModule({
state: { coins: 0, level: 1, items: [] as string[] },
mutations: {
addCoins(state, amount: number) {
state.coins += amount; // state: { coins: number, level: number, items: string[] }
state.level = '10'; // TypeScript 报错:不能将 string 赋值给 number
}
},
getters: {
isRich(state) {
return state.coins > 1000; // 返回类型自动推导为 boolean
}
}
});
// 使用时也有完整类型提示
store.state.player.coins; // 类型: number
store.getters['player/isRich']; // 类型: boolean
// commit 时有 payload 类型检查
store.commit('player/addCoins', 100); // 正确
store.commit('player/addCoins', '100'); // TypeScript 报错:参数类型不匹配显式类型标注(可选)
如果需要更严格的类型约束:
interface PlayerState {
coins: number;
level: number;
items: string[];
}
const playerModule = defineModule<PlayerState>({
state: {
coins: 0,
level: 1,
items: []
},
mutations: {
// state 类型是 PlayerState,amount 需要手动标注
addCoins(state, amount: number) {
state.coins += amount;
}
}
});高级类型
// ActionContext 类型
import type { ActionContext } from '@game-flux/core';
type PlayerContext = ActionContext<RootState, PlayerState>;
const actions = {
async buyItem(context: PlayerContext, itemId: number) {
const { state, commit, dispatch, getters } = context;
// 所有属性都有完整类型
}
};导出的类型
// 核心类型
export type {
Store, // Store 实例接口
StoreOptions, // createStore 的配置类型
ModuleOptions, // defineModule 的配置类型
MutationHandler, // Mutation 函数类型
ActionHandler, // Action 函数类型
ActionContext, // Action 上下文类型
GetterHandler, // Getter 函数类型
Subscriber, // 订阅函数类型
Unsubscriber, // 取消订阅函数类型
Plugin, // 插件接口
WatchCallback, // watch 回调函数类型
WatchOptions // watch 选项类型
};
// 导出的函数
export {
createStore, // 创建 store
defineModule, // 定义模块
produce // 不可变更新助手
};
// 导出的插件
export {
persistPlugin, // 持久化插件
loggerPlugin, // 日志插件
timeTravelPlugin, // 时间旅行插件
createStorageAdapter // 创建存储适配器
};常见问题
Q: 为什么选择不可变更新?
A:
- 性能优化 - 可以用
===快速判断状态是否变化 - 时间旅行 - 可以随时回到历史状态
- 调试友好 - 状态变化可追溯
- 避免副作用 - 防止意外修改共享状态
Q: GameFlux 的架构设计理念是什么?
A: GameFlux 遵循 YAGNI(You Aren't Gonna Need It)原则,专注游戏场景:
架构特点:
- 简化优于完美 - Getter 缓存使用简单的 Map,而非复杂的依赖追踪
- 类型安全优先 -
watch只支持函数选择器,提供完整的 TypeScript 类型推导 - 性能至上 - 避免过度抽象,直接在 Store 内部实现核心功能
实现细节:
// 推荐:函数选择器(类型安全)
store.watch(
state => state.player.coins, // IDE 自动补全,编译时检查
(newVal, oldVal) => console.log(newVal)
)
// 不支持:字符串路径(无类型检查,易出错)
// store.watch('player.coins', callback)这种设计在保持简洁的同时,提供了游戏开发所需的所有核心功能。
Q: 如何在小程序中使用?
A: GameFlux 完全兼容小程序环境:
// 微信小程序
import { createStore, persistPlugin } from '@game-flux/core';
const store = createStore({
modules: { player },
plugins: [
persistPlugin({
storage: 'wechat' // 自动使用 wx.storage
})
]
});
// 在页面中使用
Page({
onLoad() {
store.subscribe(() => {
this.setData({ playerData: store.state.player });
});
}
});Q: mutation 可以是异步的吗?
A: 不可以。mutation 必须是同步的,异步操作请使用 action:
// 错误 - mutation 中不要用异步
mutations: {
async addCoins(state, amount) { // 不要这样做!
await delay(1000);
state.coins += amount;
}
}
// 正确 - 在 action 中处理异步
actions: {
async addCoinsWithDelay({ commit }, amount) {
await delay(1000);
commit('addCoins', amount);
}
}Q: 如何调试状态变化?
A: 使用日志插件或 DevTools:
// 方式1:日志插件
const store = createStore({
plugins: [loggerPlugin()]
});
// 方式2:时间旅行
const store = createStore({
plugins: [timeTravelPlugin()]
});
store.devtools.undo(); // 撤销到上一个状态贡献
欢迎提交 Issue 和 Pull Request!
许可证
MIT License © 2024 GameFlux Team
