npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@game-flux/core

v1.0.1

Published

用于游戏开发的轻量级状态管理库

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 实例,包含 stategetterscommitdispatch 等方法 |

示例

const store = createStore({
  state: { loading: false },
  mutations: {
    setLoading(state, loading: boolean) {
      state.loading = loading;
    }
  },
  modules: {
    player: playerModule
  },
  plugins: [persistPlugin(), loggerPlugin()]
});

相关 API


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> | 模块配置对象,传递给 createStoremodules 选项 |

示例

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


store.state

获取当前状态树(只读)。

类型

readonly state: S

示例

console.log(store.state.loading);         // 访问根级状态
console.log(store.state.player.coins);    // 访问模块状态

注意事项

  • 不要直接修改 state,必须通过 mutation
  • TypeScript 会将 state 标记为只读

相关 API


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


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(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.subscribe(subscriber)

订阅所有 mutation 的变化。

类型签名

subscribe(
  subscriber: (mutation: MutationInfo, state: S, prevState: S) => void
): Unsubscriber

参数

| 参数 | 类型 | 必填 | 描述 | |------|------|:----:|------| | subscriber | Function | 是 | 订阅函数,在每次 mutation 后调用 |

订阅函数参数

| 参数 | 类型 | 描述 | |------|------|------| | mutation | MutationInfo | Mutation 信息,包含 typepayloadtimestamp | | 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(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.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.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


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


插件 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


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(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


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


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:

  1. 性能优化 - 可以用 === 快速判断状态是否变化
  2. 时间旅行 - 可以随时回到历史状态
  3. 调试友好 - 状态变化可追溯
  4. 避免副作用 - 防止意外修改共享状态

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