strict-vuex
v0.0.27
Published
A comprehensive TypeScript type system to create a fully typed Vuex store
Maintainers
Readme
Strict Vuex Type System
A comprehensive TypeScript type system for Vuex that provides complete type safety for modules, state, getters, actions, mutations, and mappers - This package will allow you to use vuex as-is but with a very strong and strict types to help you use it in a safe way.
Table of Contents
- Strict Vuex Type System
- Table of Contents
- Features
- Installation
- Naming Conventions
- Project Store Structure
- Quick Start
- Usage
- Advanced Features
- Type Definitions Reference
- API Reference
- Best Practices
- ⚡ Performance Tips
- Migration Guide
- Troubleshooting
- FAQ
Features
- ✅ Full Type Safety: Complete TypeScript support for all Vuex features with end-to-end type inference
- ✅ Nested Modules: Support for deeply nested module structures with proper type inference and state resolution
- ✅ Namespaced Modules: Full support for both namespaced (
isolated) and non-namespaced (default) modules - ✅ Root Actions: Proper typing for root-level actions in namespaced modules with
{ root: true }flag - ✅ Typed Mappers: Fully typed
mapState,mapGetters,mapActions,mapMutationshelpers with namespace support - ✅ Dynamic Module Registration: Type-safe module registration/unregistration at runtime with path validation
- ✅ Action Context: Properly typed dispatch and commit within action contexts with module-aware types
- ✅ Path Validation: Type-safe module paths for
hasModule,registerModule, andunregisterModule - ✅ Getters Resolution: Full getter type resolution across module boundaries with proper namespace prefixing
- ✅ Mutations Resolution: Complete mutation typing with namespace awareness
- ✅ Actions Resolution: Action typing with root action support and proper return type inference
- ✅ Logger Support: Typed logger plugin with filtering, transformation options, and custom logger support
- ✅ Subscription Hooks: Typed mutation and action subscription methods with proper payload types
- ✅ Watch API: Type-safe store watching capabilities with getter and callback typing
- ✅ Hot Module Replacement: Full HMR support with type safety through
hotUpdatemethod - ✅ Composition API Support: Full
useStorehook typing for Vue 3 Composition API - ✅ Store Options: Fully typed store creation options including plugins, strict mode, and devtools
- ✅ Namespaced Helpers:
createNamespacedHelperswith full type inference for bound mappers - ✅ Module Modes: Support for
isolated(namespaced) anddefault(non-namespaced) module modes - ✅ Module Tree Traversal: Automatic type resolution for complex module hierarchies
- ✅ Payload Validation: Enforced object-style payloads for better type safety and consistency
- ✅ Custom Store Properties: Extensible store types through TypeScript module augmentation
- ✅ Component Integration: Automatic
$storetyping in Vue components through module augmentation - ✅ State Functions: Support for both object and function-style state definitions
- ✅ Module Relationship Tracking: Parent-child module relationships with depth and hierarchy information
Installation
Package Installation
Install the package using your preferred package manager:
# Using npm
npm install strict-vuex -D
# Using yarn
yarn add strict-vuex -D
# Using pnpm 🤍
pnpm add strict-vuex -DTypeScript Configuration
for PNPM users - you have to add the path mapping for vuex
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"vuex": ["./node_modules/vuex/types"] // Fix for PNPM hoisting issue
}
}
}Troubleshooting Installation Issues
Module Resolution Issues
If TypeScript can't find Vuex types:
- Clear TypeScript cache:
rm -rf node_modules/.cache
rm -rf node_modules/.tmpRestart TypeScript service in your IDE:
- VSCode:
Cmd/Ctrl+Shift+P→ "TypeScript: Restart TS Server"
- VSCode:
Rebuild node_modules:
rm -rf node_modules package-lock.json
pnpm installType Declaration Conflicts
If you see duplicate identifier errors:
- Ensure you don't have multiple Vuex type declarations
- Check that skipLibCheck is set to true in tsconfig:
{
"compilerOptions": {
"skipLibCheck": true
}
}Version Compatibility
Ensure you're using compatible versions:
| Vue Version | Vuex Version | TypeScript Version | | ----------- | ------------ | ------------------ | | Vue 3.x | Vuex 4.x | TypeScript 4.5+ | | Vue 2.7 | Vuex 3.x | TypeScript 4.5+ | | Vue 2.6 | Vuex 3.x | TypeScript 3.5+ |
Post-Installation Checklist
- [ ] Vuex 4 is installed in
package.json - [ ] TypeScript configuration includes Vuex types
- [ ] PNPM path mapping is added (if using PNPM)
- [ ] Store directory structure is created
- [ ] IDE extensions are installed and configured
- [ ] TypeScript service recognizes Vuex types
- [ ] Test store creation works without type errors
Naming Conventions
| Element | Convention | Example | | ------------------- | ---------------------- | ---------------------- | | Module Files | camelCase | moduleA/index.ts | | Type Files | camelCase | types.ts | | Module Types | PascalCase | ModuleA, ModuleAA | | State Interface | PascalCase + State | ModuleAState | | Getters Interface | PascalCase + Getters | ModuleAGetters | | Actions Interface | PascalCase + Actions | ModuleAActions | | Mutations Interface | PascalCase + Mutations | ModuleAMutations | | Action Names | camelCase | fetchData, updateUser | | Mutation Names | SCREAMING_SNAKE_CASE | SET_DATA, UPDATE_USER | | Getter Names | camelCase | isLoggedIn, totalCount |
Project Store Structure
src/
└── store/
├── index.ts # Root store configuration
└── modules/
├── moduleA/
│ ├── index.ts # ModuleA definition
│ ├── types.ts # ModuleA type interfaces
│ └── modules/
│ └── moduleAA/
│ ├── index.ts # ModuleAA definition
│ ├── types.ts # ModuleAA type interfaces
│ └── modules/
│ └── moduleAAA/
│ ├── index.ts # ModuleAAA definition
│ └── types.ts # ModuleAAA type interfaces
└── moduleB/
├── index.ts # ModuleB definition
├── types.ts # ModuleB type interfaces
└── modules/
└── moduleBB/
├── index.ts # ModuleBB definition
├── types.ts # ModuleBB type interfaces
└── modules/
└── moduleBBB/
├── index.ts # ModuleBBB definition
└── types.ts # ModuleBBB type interfacesBest Practices for Structure
- Keep modules focused - Each module should handle a single feature/domain
- Consistent nesting - Follow the same structure pattern throughout
- Type-first approach - Define types before implementation
- Separate concerns - Split large modules into sub-modules
- Avoid circular dependencies - Use type-only imports when needed
- Document module purpose - Add comments explaining module responsibilities
Module Hierarchy
The module tree structure for this example:
root→moduleA→moduleAA→moduleAAAroot→moduleB→moduleBB→moduleBBB
Module Communication Pattern
┌────────────────┐
│ Root │
├────────────────┤
│ │
▼ ▼
moduleA moduleB
│ │
▼ ▼
moduleAA moduleBB
│ │
▼ ▼
moduleAAA moduleBBBFile Organization Patterns
Pattern 1: Separate Type Files (Recommended)
moduleA/
├── types.ts # Type definitions
├── state.ts # State definition
├── getters.ts # Getters
├── actions.ts # Actions
├── mutations.ts # Mutations
└── index.ts # Module assemblyPattern 2: Single File Module (Simple Modules)
store/
├── features/
│ ├── authentication/
│ ├── shopping-cart/
│ └── user-profile/
└── shared/
├── types.ts
└── utils.tsQuick Start
Understanding the _Module Type Parameters
The _Module type is the core of the type system. It accepts 7 parameters that define every aspect of your module:
interface _Module<
ModuleName extends ModuleNames, // 1. Module identifier
Mode extends ModuleMode, // 2. 'isolated' or 'default'
State extends StoreStateGeneric, // 3. State type
Getters extends StoreGettersGeneric, // 4. Getters type
Actions extends StoreActionsGeneric, // 5. Actions type
Mutations extends StoreMutationsGeneric, // 6. Mutations type
Modules = undefined // 7. Child modules (optional)
>Parameter 1: ModuleName
The module's unique identifier. Must match the key used when registering the module.
type MyModule = _Module<
'myModule', // Must be a string literal, not a variable
...
>;
// code
const myModuleInstance: MyModule = { ... }
// When registering:
const parentModule: ParentModule = {
modules: {
myModule: myModuleInstance // Key must match 'myModule'
}
}Parameter 2: Mode
Determines if the module is namespaced or not:
'isolated'→{ namespaced: true }(creates namespace)'default'→{ namespaced: false }(no namespace)
// Isolated module - creates namespace 'userModule/'
type UserModule = _Module<'userModule', 'isolated', ...>;
// code
const userModule: UserModule = {
namespaced: true,
...
}
// Default module - no namespace, merges with parent
type HelperModule = _Module<'helperModule', 'default', ...>;
// code
const helperModule: HelperModule = {
namespaced: false,
...
}Parameter 3: State
Define your state structure as an object type:
// [1] Simple state
type SimpleModule = _Module<
'simpleModule',
'isolated',
{ count: number }, // State with single property
...
>;
// code
const simpleModule: SimpleModule = {
namespaced: true,
state: { count: 5 },
...
}
// [2] Complex state
interface UserState {
currentUser: User | null;
users: User[];
loading: boolean;
filters: {
role: string;
status: 'active' | 'inactive';
};
}
type UserModule = _Module<
'userModule',
'isolated',
UserState, // Use interface for complex state
...
>;
// code
const userModule: UserModule = {
namespaced: true,
state: {
currentUser: { ... };
users: [...];
loading: true;
filters: {
role: 'admin';
status: 'active';
};
},
...
}Parameter 4: Getters
Define getter return types as key-value pairs:
// Simple getters
type Module1 = _Module<
'module1',
'isolated',
{ count: number },
{
doubleCount: number; // Simple return type
isPositive: boolean; // Boolean return
formattedCount: (prefix: string) => string; // Parameterized getter
},
...
>;
// code
const module1: Module1 = {
namespace: true,
{ count: 7 },
{
doubleCount(state) {
return state.count * 2;
},
isPositive(state) {
return state.count > 0;
},
formattedCount: (state) => (prefix: string) => `${prefix}${state.count}`;
},
...
}Parameter 5: Actions
Use StoreActionRecord<Payload, Return, RootLevel> to define actions:
interface StoreActionRecord<
Payload,
Return,
RootLevel extends boolean = false
> {
payload: Payload; // the action payload type
return: Return; // the action return type
root: RootLevel; // if true the action will be a root level action
}type MyModule = _Module<
'myModule',
'isolated',
State,
Getters,
{
// Simple action - no payload - no return
fetchData: StoreActionRecord<null, void>;
// Action with payload
updateUser: StoreActionRecord<
{ id: string; data: Partial<User> }, // Payload type
User // Return type
>;
// Root-level action (accessible globally)
globalNotify: StoreActionRecord<
{ message: string },
void,
true // Root level flag
>;
},
...
>;
// code
const myModule: MyModule = {
namespaced: true,
{ ... }, // State
{ ... }, // Getters
{
fetchData({ dipatch }) {
// Calling the globalNotify action with root level flag
dipatch('globalNotify', { message: 'msg' }, { root: true }); // ✅
// Calling it like this won't work
dipatch('globalNotify', { message: 'msg' }); // ❌
// You can use this which pointing to the store instance
this.state // root level state
},
updateUser(ctx, payload) {
return { ... }; // User
}
// Must define it like this or you will git a TypeScript error
globalNotify: {
root: true,
handler: (ctx, payload) => {}
}
}
}Parameter 6: Mutations
Mutations are simple key-value pairs with payload types:
type MyModule = _Module<
'myModule',
'isolated',
State,
Getters,
Actions,
{
// Use SCREAMING_SNAKE_CASE for mutation names
SET_LOADING: { loading: boolean };
UPDATE_USER: { user: User };
ADD_ITEM: { item: Item };
DELETE_ITEM: { id: string };
RESET_STATE: null; // No payload
},
...
>;
// code
const myModule: MyModule = {
namespaced: true,
{ ... },
{ ... },
{ ... },
{
SET_LOADING(state, payload) {
console.log(payload.loading);
}
...
}
}Parameter 7: Modules (Child Modules)
Define nested modules or use undefined for leaf modules:
// Leaf module (no children)
type LeafModule = _Module<
"leaf",
"isolated",
State,
Getters,
Actions,
Mutations,
undefined // No child modules
>;
// Parent module with children
type ParentModule = _Module<
"parent",
"isolated",
State,
Getters,
Actions,
Mutations,
{
childA: ChildModuleA; // Child module type
childB: ChildModuleB; // Another child
optionalChild?: OptionalModule; // Optional child module (used with module registed with registerModule() API at runtime)
}
>;Empty Parameter
If any parameter of the 7 parameters is empty and you won't assign any data to it
all you have to do is just assign a type never to in the type declaration.
type ModuleWithNoGetters = _Module<
'moduleWithNoGetters',
'isolated',
State,
never, // Empty getters
Actions,
Mutations,
Modules
>;
const moduleWithNoGetters: ModuleWithNoGetters = {
namespaced: true,
state: { ... },
// no Getters
Mutations: { ... },
actions: { ... },
modules: { ... },
}Setup Step By Step
→ You need to make the type system see your modules tree types 👀
The key to making the Vuex type system work is proper module registration in the type declarations. Follow these steps to set up your typed store:
Step 1: Define Your Module Types
Start from the deepest modules and work your way up to the root:
root → moduleA → moduleAA → moduleAAA
// src/store/modules/moduleA/modules/moduleAA/modules/moduleAAA/types.ts
import type { _Module, StoreActionRecord } from "strict-vuex";
export type ModuleAAA = _Module<
"moduleAAA",
"isolated",
{ valueAAA: string },
{ getterAAA: string },
{ actionAAA: StoreActionRecord<{ data: string }, void> },
{ SET_AAA: { value: string } },
undefined // No child modules
>;// src/store/modules/moduleA/modules/moduleAA/types.ts
import type { ModuleAAA } from "./modules/moduleAAA/types";
export type ModuleAA = _Module<
"moduleAA",
"default",
{ valueAA: number },
{ getterAA: number },
{ actionAA: StoreActionRecord<{ val: number }, string> },
{ SET_AA: { value: number } },
{ moduleAAA: ModuleAAA } // Include child module type
>;// src/store/modules/moduleA/types.ts
import type { ModuleAA } from "./modules/moduleAA/types";
export type ModuleA = _Module<
"moduleA",
"isolated",
{ valueA: boolean },
{ getterA: string },
{ actionA: StoreActionRecord<{ id: string }, boolean> },
{ SET_A: { value: boolean } },
{ moduleAA: ModuleAA } // Include child module type
>;Step 2: Register Modules in Type Declaration (CRITICAL STEP)
This is the most important step 🔥 - Register your modules at the VuexStoreRootModules interface in to actualy make the system see you types - This is the step when the intellisense magical effect will start:
Important Note: You have to do this step before you start write you actual modules code in step 3
And you can use the next interfaces to define the root data if you want too:
VuexStoreRootState, VuexStoreRootGetters, VuexStoreRootActions, VuexStoreRootMutations
And you can do this by simply extending the strict-vuex module in the app and i choose to extend it in the src/store/types.d.ts file which contains the root level data:
// src/store/index.ts
import { moduleA } from "@/store/modules/moduleA";
import { moduleB } from "@/store/modules/moduleB";
const store = createStore({
modules: { moduleA, moduleB },
});// src/store/types.d.ts
import type { ModuleA } from "@/store/modules/moduleA/types";
import type { ModuleB } from "@/store/modules/moduleB/types";
declare module "strict-vuex" {
// Root level configurations
interface VuexStoreRootState {
rootValue: string;
}
interface VuexStoreRootGetters {
rootGetter: string;
}
interface VuexStoreRootActions {
rootAction: StoreActionRecord<{ data: string }, void>;
}
interface VuexStoreRootMutations {
SET_ROOT: { value: string };
}
// ⚠️ CRITICAL: Register your root modules here (only root modules)
// For example if we have the next module tree:
// root -> moduleA -> moduleAA -> moduleAAA
// root -> moduleB -> moduleBB -> moduleBBB
// So, you only need to define the root modules only like this
// This is the step when the intellisense magical effect will start
interface VuexStoreRootModules {
moduleA: ModuleA; // The key must match the module name in store
moduleB: ModuleB;
}
}Step 3: Implement Your Modules
Starting from this step you will find that vuex system is fully typed and TypeScipt will force you to write the right code as your types defined before 😎🥲
// src/store/modules/moduleA/modules/moduleAA/modules/moduleAAA/index.ts
import type { ModuleAAA } from "./types";
const moduleAAA: ModuleAAA = {
namespaced: true, // isolated = true
state: {
valueAAA: "AAA",
},
getters: {
getterAAA: (state) => state.valueAAA,
},
actions: {
actionAAA: async ({ commit }, payload) => {
commit("SET_AAA", { value: payload.data });
},
},
mutations: {
SET_AAA(state, payload) {
state.valueAAA = payload.value;
},
},
};
export default moduleAAA;
// And just do the same for other modules...Step 4: Create the Store
// src/store/index.ts
import { createStore } from "vuex";
import moduleA from "./modules/moduleA";
import moduleB from "./modules/moduleB";
const store = createStore({
state: {
rootValue: "root",
},
getters: {
rootGetter: (state) => state.rootValue,
},
actions: {
rootAction: async ({ commit }, payload) => {
commit("SET_ROOT", { value: payload.data });
},
},
mutations: {
SET_ROOT(state, payload) {
state.rootValue = payload.value;
},
},
modules: {
moduleA, // Key must match VuexStoreRootModules interface
moduleB,
},
});
export default store;Step 5: Install The Store Instance As A Plugin
// src/main.ts
import { createApp } from 'vue';
import vuexStore from `./store`;
const app = createApp({ ... });
app.use(vuexStore);Step 6: Verify Type System is Working
The type system will now provide full IntelliSense - and you will find that it's so easy to use vuex
store.state.rootValue; // string
store.state.moduleA.valueA; // boolean
store.state.moduleA.moduleAA.valueAA; // number
store.state.moduleA.moduleAA.moduleAAA.valueAAA; // string
// Namespaced access for isolated modules
store.getters["moduleA/getterA"]; // string
store.getters["moduleA/getterAA"]; // number (default module, no namespace)
store.getters["moduleA/moduleAAA/getterAAA"]; // string
// Actions with proper payload types
store.dispatch("rootAction", { data: "test" });
store.dispatch("moduleA/actionA", { id: "123" });
store.dispatch("moduleA/moduleAAA/actionAAA", { data: "test" });How the Type System Works
- Module Definition: Each module is typed with
_Modulegeneric - Tree Building: Child modules are included in parent's type definition
- Registration: Root modules MUST be registered in
VuexStoreRootModules - Resolution: The type system automatically resolves the entire tree
- IntelliSense: Full autocomplete and type checking throughout your app
Common Mistakes to Avoid
- ❌ Forgetting to register modules in
VuexStoreRootModules - ❌ Module key mismatch between type and implementation
- ❌ Wrong module name in _Module first parameter
- ❌ Missing child module types in parent definition
Usage
Direct Store Access
Direct store access provides fully typed access to state, getters, actions, and mutations throughout your application.
OPTIONS API
Short example for Options API usage
<script lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions, mapMutations } from "vuex";
export default defineComponent({
computed: {
moduleBVal: () => this.$store.state.moduleA.moduleB.value,
},
methods: {
increment(value: number) {
await this.$store.dispatch("moduleA/increment", { value });
},
resetCount() {
this.$store.commit("moduleA/RESET_COUNT");
},
},
});
</script>
<template>
<div>
<p>{{ $store.state.moduleA.count }}</p>
<p>{{ moduleBVal }}</p>
<p>{{ $store.getters['moduleA/doubleCount'] }}</p>
<button @click="increment(10)">+</button>
<button @click="resetCount">0</button>
</div>
</template>COMPOSITION API
Short example for Composition API usage (recommended):
<script setup lang="ts">
import { computed } from "vue";
import { useStore } from "vuex";
const store = useStore();
const rootValue = computed(() => store.state.rootValue);
const doActionA = (p: any) => store.dispatch("moduleA/actionA", p);
</script>
<template>
<div>{{ rootValue }}</div>
</template>Type Safety in Both APIs
// ✅ TypeScript provides full IntelliSense in both APIs
// Options API
this.$store.state.moduleA.? // Autocomplete shows: valueA, moduleAA
this.$store.dispatch('?') // Autocomplete shows available actions
// Composition API
store.state.moduleA.? // Autocomplete shows: valueA, moduleAA
store.dispatch('?') // Autocomplete shows available actions
// ❌ TypeScript catches errors at compile time
// Options API errors
this.$store.state.moduleA.wrongProperty; // Error: Property doesn't exist
this.$store.dispatch('wrongAction', {}); // Error: Action doesn't exist
// Composition API errors
store.state.moduleA.wrongProperty; // Error: Property doesn't exist
store.dispatch('wrongAction', {}); // Error: Action doesn't existUsing Mappers in Components
Mappers provide a clean way to bind store state, getters, actions, and mutations to component properties and methods.
OPTIONS API
This concise Options API example demonstrates common mapper usages:
<script lang="ts">
import { defineComponent } from "vue";
import { mapState, mapActions, mapMutations } from "vuex";
export default defineComponent({
computed: {
...mapState(["rootValue"]),
...mapState("moduleA", { aValue: "valueA" }),
},
methods: {
...mapActions(["rootAction"]),
...mapActions("moduleA", ["actionA"]),
...mapMutations(["SET_ROOT", "moduleA/SET_A"]),
},
});
</script>
<template>
<div>
<p>{{ rootValue }}</p>
<p>{{ aValue }}</p>
<button @click="rootAction({ data: 'ok' })">Action</button>
</div>
</template>COMPOSITION API
No need to use mappers in Composition API
<script setup lang="ts">
import { computed } from "vue";
import { useStore } from "vuex";
const store = useStore();
const rootValue = computed(() => store.state.rootValue);
const doActionA = (p: any) => store.dispatch("moduleA/actionA", p);
</script>
<template>
<div>{{ rootValue }}</div>
</template>Namespaced Helpers
Create typed helper functions for a specific namespaced module using createNamespacedHelpers. This is useful when you need to access the same module frequently in a component.
Basic Usage
import { createNamespacedHelpers } from "vuex";
// Create helpers for a specific namespace
const { mapState, mapGetters, mapActions, mapMutations } =
createNamespacedHelpers("moduleA");
export default {
computed: {
// All mappers are scoped to 'moduleA'
...mapState(["a"]), // maps to moduleA state
...mapGetters(["getterA"]), // maps to moduleA getters
},
methods: {
...mapActions(["actionA"]), // maps to moduleA actions
...mapMutations(["mutationA"]), // maps to moduleA mutations
},
};With Nested Modules
// For deeply nested modules, use the full path
const { mapState, mapActions } = createNamespacedHelpers("moduleA/moduleAA");
export default {
computed: {
...mapState(["aa"]), // moduleA/moduleAA state
},
methods: {
...mapActions(["actionAA"]), // moduleA/moduleAA actions
},
};Array and Object Syntax
const { mapState, mapActions } = createNamespacedHelpers("moduleA");
export default {
computed: {
// Array syntax - uses same name
...mapState(["a"]),
// Object syntax - rename properties
...mapState({
moduleAValue: "a",
customName: (state) => state.a.toUpperCase(),
}),
},
methods: {
// Array syntax
...mapActions(["actionA"]),
// Object syntax - rename or use custom functions
...mapActions({
doActionA: "actionA",
customAction: (dispatch, payload) => {
return dispatch("actionA", payload);
},
}),
},
};Benefits
- Less repetition: No need to prefix every mapper with the namespace
- Full type safety: All mappers are typed to the specific module
- Cleaner code: Easier to read when working with a single module extensively
Root Actions in Namespaced Modules
Root actions allow namespaced modules to expose actions globally, making them accessible without the namespace prefix.
Defining Root Actions
// src/store/modules/moduleA/modules/moduleAA/modules/moduleAAA/types.ts
import type { _Module, StoreActionRecord } from "strict-vuex";
export type ModuleAAA = _Module<
"moduleAAA",
"isolated",
{ valueAAA: string },
{ getterAAA: string },
{
// Local action - requires namespace
localActionAAA: StoreActionRecord<{ data: string }, void>;
// Root action - accessible globally
globalActionAAA: StoreActionRecord<{ message: string }, boolean, true>; // true = root
// Another root action
notifyAllModules: StoreActionRecord<
{ alert: string; level: "info" | "error" },
void,
true
>;
},
{
SET_AAA: { value: string };
NOTIFY: { message: string };
},
undefined
>;Implementing Root Actions
// src/store/modules/moduleA/modules/moduleAA/modules/moduleAAA/index.ts
import type { ModuleAAA } from "./types";
const moduleAAA: ModuleAAA = {
namespaced: true,
state: {
valueAAA: "initial",
},
getters: {
getterAAA: (state) => state.valueAAA,
},
actions: {
// Local action - simple function
localActionAAA: async ({ commit }, payload) => {
commit("SET_AAA", { value: payload.data });
},
// Root action - MUST be an object with root: true
globalActionAAA: {
root: true, // Required for root actions
handler: async (
{ commit, dispatch, rootState, rootGetters },
payload
) => {
// Can access root state and getters directly
console.log("Root state:", rootState.rootValue);
console.log("Root getter:", rootGetters.rootGetter);
// Can dispatch other root actions without { root: true }
await dispatch("rootAction", { data: "from globalActionAAA" });
// Can commit root mutations without { root: true }
commit("SET_ROOT", { value: "updated from moduleAAA" });
// Can still commit local mutations
commit(
"moduleA/moduleAAA/SET_AAA",
{ value: payload.message },
{ root: true }
);
return true;
},
},
// Another root action
notifyAllModules: {
root: true,
handler: async ({ dispatch, commit }, payload) => {
// Notify all modules
await dispatch("moduleA/handleNotification", payload, { root: true });
await dispatch("moduleB/handleNotification", payload, { root: true });
// Update global notification state
commit("SET_GLOBAL_NOTIFICATION", payload, { root: true });
},
},
},
mutations: {
SET_AAA(state, payload) {
state.valueAAA = payload.value;
},
NOTIFY(state, payload) {
console.log("Notification:", payload.message);
},
},
};
export default moduleAAA;Accessing Root Actions
// Options API
export default {
methods: {
async handleActions() {
// Root action - no namespace needed
const result = await this.$store.dispatch("globalActionAAA", {
message: "Hello from anywhere",
});
// Also accessible with full path + root option
await this.$store.dispatch(
"moduleA/moduleAAA/globalActionAAA",
{ message: "Hello" },
{ root: true }
);
// Local action - requires namespace
await this.$store.dispatch("moduleA/moduleAAA/localActionAAA", {
data: "test",
});
// Another root action
await this.$store.dispatch("notifyAllModules", {
alert: "System update",
level: "info",
});
},
},
};
// Composition API
const store = useStore();
// Root action - no namespace
await store.dispatch("globalActionAAA", { message: "Hello" });
// Local action - with namespace
await store.dispatch("moduleA/moduleAAA/localActionAAA", { data: "test" });
// Root action from deeply nested module
await store.dispatch("notifyAllModules", {
alert: "Update",
level: "error",
});Root Actions with Mappers
<script lang="ts">
import { mapActions } from 'vuex';
export default {
methods: {
// Map root actions directly (no namespace)
...mapActions([
'globalActionAAA', // Root action from moduleAAA
'notifyAllModules', // Another root action
'rootAction' // Root level action
]),
// Map from namespace (for root actions, need special handling)
...mapActions('moduleA/moduleAAA', [
'localActionAAA', // Local action
'globalActionAAA' // Root action (requires { root: true } when called)
]),
async handleMappedActions() {
// Root actions mapped globally
await this.globalActionAAA({ message: 'test' });
await this.notifyAllModules({ alert: 'info', level: 'info' });
// Local action from namespace mapping
await this.localActionAAA({ data: 'test' });
// Root action from namespace mapping - needs { root: true }
await this.globalActionAAA(
{ message: 'test' },
{ root: true } // Required when mapped from namespace
);
}
}
};
</script>Root Actions in Action Context
// In any module action
const someModule: Module = {
actions: {
someAction: async ({ dispatch }) => {
// Dispatch root action from moduleAAA
await dispatch("globalActionAAA", { message: "test" }, { root: true });
// Or without root flag since it's a root action
await dispatch("globalActionAAA", { message: "test" });
// Dispatch local action from another module
await dispatch(
"moduleA/moduleAAA/localActionAAA",
{ data: "test" },
{ root: true }
);
},
},
};Important Rules for Root Actions
// ✅ CORRECT: Root action as object
globalAction: {
root: true,
handler: async (context, payload) => {
// Implementation
}
}
// ❌ WRONG: Root action as function
globalAction: async (context, payload) => {
// This won't work as root action
}
// ✅ CORRECT: Type definition with root flag
globalAction: StoreActionRecord<PayloadType, ReturnType, true>
// ❌ WRONG: Missing root flag in type
globalAction: StoreActionRecord<PayloadType, ReturnType> // defaults to falseDynamic Module Registration
The type system provides full support for registering and unregistering modules at runtime with type-safe paths.
Module Registration Basics
// Register top-level modules (depth 0)
store.registerModule("moduleC", moduleCInstance);
store.registerModule(["moduleC"], moduleCInstance); // Array notation also works
// Register nested modules (depth 1+)
store.registerModule(["moduleA", "moduleAC"], moduleACInstance);
store.registerModule(["moduleB", "moduleBC"], moduleBCInstance);
// Register deeply nested modules
store.registerModule(["moduleA", "moduleAA", "moduleAAC"], moduleAACInstance);
store.registerModule(["moduleB", "moduleBB", "moduleBBC"], moduleBBCInstance);
// ❌ Wrong: Can't use string for nested modules
store.registerModule("moduleA/moduleAC", moduleACInstance); // Error
store.registerModule("moduleA.moduleAC", moduleACInstance); // ErrorDynamic Module Definition
// Define a dynamic module type
import type { _Module, StoreActionRecord } from "strict-vuex";
export type DynamicModuleC = _Module<
"moduleC",
"isolated",
{ valueC: string; dynamicData: any[] },
{ getterC: string; dataCount: number },
{
loadDynamicData: StoreActionRecord<{ source: string }, any[]>;
clearData: StoreActionRecord<null, void>;
},
{
SET_C: { value: string };
SET_DYNAMIC_DATA: { data: any[] };
},
undefined // No children initially
>;
const createDynamicModule = (initialValue: string): DynamicModuleC => ({
namespaced: true,
state: {
valueC: initialValue,
dynamicData: [],
},
getters: {
getterC: (state) => state.valueC,
dataCount: (state) => state.dynamicData.length,
},
actions: {
loadDynamicData: async ({ commit }, payload) => {
const data = await fetchData(payload.source);
commit("SET_DYNAMIC_DATA", { data });
return data;
},
clearData: async ({ commit }) => {
commit("SET_DYNAMIC_DATA", { data: [] });
},
},
mutations: {
SET_C(state, payload) {
state.valueC = payload.value;
},
SET_DYNAMIC_DATA(state, payload) {
state.dynamicData = payload.data;
},
},
});Registering Modules at Runtime
// Options API
export default {
methods: {
async loadFeatureModule() {
// Check if module exists
if (!this.$store.hasModule("moduleC")) {
// Create and register module
const moduleC = createDynamicModule("initial");
this.$store.registerModule("moduleC", moduleC);
// Module is now accessible
await this.$store.dispatch("moduleC/loadDynamicData", {
source: "api",
});
const value = this.$store.state.moduleC.valueC; // typed
}
},
async loadNestedModule() {
// Check nested module
if (!this.$store.hasModule(["moduleA", "moduleAC"])) {
const moduleAC = createNestedModule();
this.$store.registerModule(["moduleA", "moduleAC"], moduleAC);
// Access nested dynamic module
const state = this.$store.state.moduleA.moduleAC; // typed if defined
}
},
unloadModule() {
if (this.$store.hasModule("moduleC")) {
// Save state if needed
const state = this.$store.state.moduleC;
localStorage.setItem("moduleC", JSON.stringify(state));
// Unregister module
this.$store.unregisterModule("moduleC");
}
},
},
};
// Composition API
const store = useStore();
async function loadDynamicModule() {
if (!store.hasModule("moduleC")) {
const moduleC = createDynamicModule("init");
store.registerModule("moduleC", moduleC);
}
}
async function loadNestedDynamic() {
// Register nested module in moduleB tree
if (!store.hasModule(["moduleB", "moduleBC"])) {
const moduleBC = await import("./modules/moduleBC");
store.registerModule(["moduleB", "moduleBC"], moduleBC.default);
}
}Module Registration Options
interface ModuleOptions {
preserveState?: boolean; // Preserve existing state
}
// Register with options
store.registerModule("moduleC", moduleCInstance, {
preserveState: true, // Keep existing state if hot reloading
});
// For nested modules
store.registerModule(["moduleA", "moduleAC"], moduleACInstance, {
preserveState: localStorage.getItem("moduleAC") !== null,
});Checking Module Existence
// Check top-level modules
const hasModuleA = store.hasModule("moduleA"); // true
const hasModuleC = store.hasModule("moduleC"); // false (not registered yet)
// Check nested modules - MUST use array notation
const hasModuleAA = store.hasModule(["moduleA", "moduleAA"]); // true
const hasModuleAAA = store.hasModule(["moduleA", "moduleAA", "moduleAAA"]); // true
// Check dynamic modules
const hasModuleAC = store.hasModule(["moduleA", "moduleAC"]); // false initially
// ❌ Wrong: Can't use string path for nested modules
// store.hasModule('moduleA/moduleAA'); // Always returns false
// store.hasModule('moduleA.moduleAA'); // Always returns false
// Helper function for safe access
function safeModuleAccess<T>(
store: Store,
path: string | string[],
callback: () => T,
fallback: T
): T {
if (store.hasModule(path)) {
return callback();
}
return fallback;
}
// Usage
const value = safeModuleAccess(
store,
"moduleC",
() => store.state.moduleC.valueC,
"default value"
);Unregistering Modules
// Unregister top-level module
store.unregisterModule("moduleC");
// Unregister nested module
store.unregisterModule(["moduleA", "moduleAC"]);
store.unregisterModule(["moduleB", "moduleBB", "moduleBBC"]);
// Clean up before unregistering
function cleanUnregister(path: string | string[]) {
if (store.hasModule(path)) {
// Dispatch cleanup action if exists
const modulePath = Array.isArray(path) ? path.join("/") : path;
try {
store.dispatch(`${modulePath}/cleanup`, null);
} catch (e) {
// Cleanup action might not exist
}
// Unregister
store.unregisterModule(path);
}
}
// Unregister multiple modules
function unregisterFeature(feature: string) {
const modulesToRemove = {
analytics: [
"analytics",
["analytics", "reports"],
["analytics", "tracking"],
],
chat: [
["features", "chat"],
["features", "chat", "messages"],
],
};
const paths = modulesToRemove[feature] || [];
paths.forEach((path) => cleanUnregister(path));
}Hot Module Replacement
// Hot reload for dynamic modules
if (import.meta.hot) {
// Preserve state during development
import.meta.hot.accept(["./modules/moduleC"], () => {
const newModuleC = require("./modules/moduleC").default;
// Update or register module
if (store.hasModule("moduleC")) {
store.hotUpdate({
modules: {
moduleC: newModuleC,
},
});
} else {
store.registerModule("moduleC", newModuleC, { preserveState: true });
}
});
}Advanced Features
Module Modes
Vuex modules support two modes that affect how state, getters, actions, and mutations are accessed.
Isolated Mode (namespaced: true)
Isolated modules create a separate namespace, requiring the full path to access their members.
type ModuleA = _Module
'moduleA',
'isolated', // Creates namespace
{ a: string },
{ getterA: string },
{ actionA: StoreActionRecord<{ val: string }, string> },
{ mutationA: { val: string } }
>;
const moduleA: ModuleA = {
namespaced: true, // Must be true for 'isolated' mode
state: { a: 'value' },
getters: {
getterA(state) {
return state.a;
}
},
actions: {
actionA(ctx, payload) {
ctx.commit('mutationA', { val: 'new value' });
return payload?.val ?? 'A';
}
},
mutations: {
mutationA(state, payload) {
state.a = payload?.val ?? 'default';
}
}
};Access Pattern:
// In components
store.state.moduleA.a; // ✓
store.getters["moduleA/getterA"]; // ✓
store.dispatch("moduleA/actionA", { val: "x" }); // ✓
store.commit("moduleA/mutationA", { val: "x" }); // ✓Default Mode (namespaced: false)
Default modules merge their members into the parent scope (usually root).
type ModuleB = _Module
'moduleB',
'default', // No namespace
{ b: string },
{ getterB: string },
{ actionB: StoreActionRecord<{ val: string }, string> },
{ mutationB: { val: string } }
>;
const moduleB: ModuleB = {
namespaced: false, // Must be false for 'default' mode
state: { b: 'value' },
getters: {
getterB(state, getters, rootState, rootGetters) {
// Has access to all root getters
return state.b + rootGetters.someRootGetter;
}
},
actions: {
actionB(ctx, payload) {
// Can access root actions and mutations directly
ctx.dispatch('someRootAction', null);
ctx.commit('someRootMutation', null);
return payload?.val ?? 'B';
}
},
mutations: {
mutationB(state, payload) {
state.b = payload?.val ?? 'default';
}
}
};Access Pattern:
// In components
store.state.moduleB.b; // ✓ State still nested
store.getters.getterB; // ✓ Getters at root level
store.dispatch("actionB", { val: "x" }); // ✓ Actions at root level
store.commit("mutationB", { val: "x" }); // ✓ Mutations at root levelKey Differences:
| Feature | Isolated Mode | Default Mode |
| ------------------- | ----------------------------------- | --------------------------- |
| State Access | store.state.moduleA.a | store.state.moduleB.b |
| Getters Access | store.getters['moduleA/getterA'] | store.getters.getterB |
| Actions Access | store.dispatch('moduleA/actionA') | store.dispatch('actionB') |
| Mutations Access | store.commit('moduleA/mutationA') | store.commit('mutationB') |
| Root Access | Limited | Full |
| Namespace Pollution | None | Yes |
Recommendation: Use isolated mode for better organization and type safety, especially in large applications.
Logger Plugin
The createLogger plugin helps debug your Vuex store by logging mutations and actions with full type safety.
Basic Usage
import { createStore, createLogger } from "vuex";
export const store = createStore({
state: () => ({ count: 0 }),
mutations: {
INCREMENT: (state) => state.count++,
},
plugins: [createLogger()],
});Advanced Configuration
import { createLogger } from "vuex";
const loggerPlugin = createLogger({
// Collapse log groups (default: true)
collapsed: false,
// Filter which mutations to log
filter(mutation, stateBefore, stateAfter) {
// Only log INCREMENT mutations
return mutation.type === "INCREMENT";
},
// Transform state before logging
transformer(state) {
// Hide sensitive data
return {
...state,
password: "***",
};
},
// Transform mutation before logging
mutationTransformer(mutation) {
return {
type: mutation.type,
payload: mutation.payload,
};
},
// Log actions (default: true)
logActions: true,
// Filter which actions to log
actionFilter(action, state) {
// Exclude certain actions
return action.type !== "fetchSensitiveData";
},
// Transform action before logging
actionTransformer(action) {
return {
type: action.type,
payload: action.payload,
};
},
// Log mutations (default: true)
logMutations: true,
// Custom logger (default: console)
logger: {
log: (message, color, payload) => {
console.log(`%c ${message}`, `color: ${color}`, payload);
},
group: console.group,
groupCollapsed: console.groupCollapsed,
groupEnd: console.groupEnd,
},
});
export const store = createStore({
// ... store options
plugins: [loggerPlugin],
});Type-Safe Filtering
const loggerPlugin = createLogger<
StoreRootStateResolved,
StoreRootGettersResolved
>({
filter(mutation, stateBefore, stateAfter) {
// Full intellisense for mutation types
if (mutation.type === "UPDATE_NAME") {
// mutation.payload is typed as { fname: string; lname: string }
console.log("Name updated to:", mutation.payload.fname);
return true;
}
return false;
},
actionFilter(action, state) {
// Full intellisense for action types
if (action.type === "updateAge") {
// action.payload is typed as { value: number }
return action.payload.value > 18;
}
return true;
},
});Console Output Example:
▼ mutation UPDATE_NAME @ 10:30:15
▶ prev state: { fname: 'John', lname: 'Doe', age: 25 }
▶ mutation: { type: 'UPDATE_NAME', payload: { fname: 'Jane', lname: 'Smith' } }
▶ next state: { fname: 'Jane', lname: 'Smith', age: 25 }Action Context
Action handlers receive a context object with full type safety based on the module's scope.
Root Level Actions
interface VuexStoreRootActions {
updateUser: StoreActionRecord<{ id: string; name: string }, User>;
}
// In store definition
actions: {
async updateUser(ctx, payload) {
// ctx has access to everything at root level
ctx.state.fname; // ✓ Root state
ctx.getters.fullName; // ✓ Root getters
ctx.commit('UPDATE_NAME', { ... }); // ✓ Root mutations
ctx.dispatch('updateAge', { ... }); // ✓ Root actions
ctx.rootState.fname; // ✓ Same as ctx.state
ctx.rootGetters.fullName; // ✓ Same as ctx.getters
return { id: payload.id, name: payload.name };
}
}Isolated Module Actions
type ModuleA = _Module
'moduleA',
'isolated',
{ a: string },
{ getterA: string },
{ actionA: StoreActionRecord<{ val: string }, string> },
{ mutationA: { val: string } }
>;
const moduleA: ModuleA = {
namespaced: true,
actions: {
actionA(ctx, payload) {
// Local scope access (no prefix needed)
ctx.state.a; // ✓ Module state
ctx.getters.getterA; // ✓ Module getters
ctx.commit('mutationA', { val: 'x' }); // ✓ Module mutations
ctx.dispatch('actionA', { val: 'y' }); // ✓ Module actions
// Root scope access
ctx.rootState.fname; // ✓ Root state
ctx.rootGetters.fullName; // ✓ Root getters
// Access root mutations/actions (requires { root: true })
ctx.commit('UPDATE_NAME', { ... }, { root: true });
ctx.dispatch('updateAge', { ... }, { root: true });
return payload?.val ?? 'A';
}
}
};Default Module Actions
type ModuleB = _Module
'moduleB',
'default',
{ b: string },
{ getterB: string },
{ actionB: StoreActionRecord<{ val: string }, string> },
{ mutationB: { val: string } }
>;
const moduleB: ModuleB = {
namespaced: false,
actions: {
actionB(ctx, payload) {
// Module state (nested)
ctx.state.b; // ✓ Module state
// Everything else is at root level
ctx.getters.getterB; // ✓ Module + root getters merged
ctx.getters.fullName; // ✓ Root getters accessible
ctx.commit('mutationB', { val: 'x' }); // ✓ No prefix needed
ctx.commit('UPDATE_NAME', { ... }); // ✓ Root mutations accessible
ctx.dispatch('actionB', { val: 'y' }); // ✓ No prefix needed
ctx.dispatch('updateAge', { ... }); // ✓ Root actions accessible
return payload?.val ?? 'B';
}
}
};Context Properties
| Property | Type | Description |
| ------------- | -------------- | ------------------------------------------------------- |
| state | Module State | Current module's state (or root state for root actions) |
| getters | Module Getters | Available getters based on scope |
| rootState | Root State | Always the complete root state tree |
| rootGetters | Root Getters | Always all root getters |
| commit | Function | Commit mutations (scoped based on module mode) |
| dispatch | Function | Dispatch actions (scoped based on module mode) |
Nested Module Access
Access state, getters, mutations, and actions from nested modules with full type safety.
State Access
// Module hierarchy: root -> moduleA -> moduleAA -> moduleAAA
// Access nested module state
store.state.moduleA.a; // ✓ ModuleA state
store.state.moduleA.moduleAA.aa; // ✓ ModuleAA state
store.state.moduleA.moduleAA.moduleAAA.aaa; // ✓ ModuleAAA stateGetters Access (Isolated Modules)
// All modules are isolated (namespaced: true)
store.getters["moduleA/getterA"]; // ✓
store.getters["moduleA/moduleAA/getterAA"]; // ✓
store.getters["moduleA/moduleAA/moduleAAA/getterAAA"]; // ✓Actions and Mutations (Isolated Modules)
// Dispatch nested actions
store.dispatch("moduleA/actionA", { val: "x" });
store.dispatch("moduleA/moduleAA/actionAA", { val: "y" });
store.dispatch("moduleA/moduleAA/moduleAAA/actionAAA", { val: "z" });
// Commit nested mutations
store.commit("moduleA/mutationA", { val: "x" });
store.commit("moduleA/moduleAA/mutationAA", { val: "y" });
store.commit("moduleA/moduleAA/moduleAAA/mutationAAA", { val: "z" });Cross-Module Communication
// In moduleAAA action
actions: {
actionAAA(ctx, payload) {
// Access parent module (moduleAA)
ctx.state.aa; // ✓ Parent state
ctx.getters['moduleA/moduleAA/getterAA']; // ✓ Parent getters
ctx.commit('moduleA/moduleAA/mutationAA', { ... }, { root: true });
// Access root
ctx.rootState.fname; // ✓ Root state
ctx.rootGetters.fullName; // ✓ Root getters
ctx.commit('UPDATE_NAME', { ... }, { root: true });
// Access sibling modules (if they exist)
ctx.dispatch('moduleA/moduleAB/actionAB', { ... }, { root: true });
return 'AAA';
}
}Type-Safe Module State Resolution
// Get complete state tree for a specific module by name
type ModuleAState = ResolveModuleStateByName<"moduleA">;
// Result: { a: string, moduleAA: { aa: string, moduleAAA: { aaa: string } } }
type ModuleAAState = ResolveModuleStateByName<"moduleAA">;
// Result: { aa: string, moduleAAA: { aaa: string } }
// Usage in components or utilities
function getModuleAState(store: _Store): ModuleAState {
return store.state.moduleA;
}Accessing Nested Modules in Context
// In moduleA action
actions: {
actionA(ctx, payload) {
// Access child module state
ctx.state.moduleAA.aa; // ✓ Child state
ctx.state.moduleAA.moduleAAA.aaa; // ✓ Grandchild state
// Dispatch to child modules
ctx.dispatch('moduleAA/actionAA', { val: 'x' });
ctx.dispatch('moduleAA/moduleAAA/actionAAA', { val: 'y' });
// Commit to child modules
ctx.commit('moduleAA/mutationAA', { val: 'x' });
return 'A';
}
}Store Subscriptions
// Subscribe to mutations
const unsubscribeMutation = store.subscribe((mutation, state) => {
// Fully typed mutation payload
console.log("Mutation:", mutation.type);
console.log("Payload:", mutation.payload);
console.log("New State:", state);
// Type narrowing
if (mutation.type === "SET_USER") {
console.log("User set to:", mutation.payload.username);
}
});
// Subscribe to actions
const unsubscribeAction = store.subscribeAction({
before: (action, state) => {
console.log("Before action:", action.type);
console.log("Payload:", action.payload);
},
after: (action, state) => {
console.log("After action:", action.type);
},
error: (action, state, error) => {
console.error("Action error:", action.type, error);
},
});
// Simple action subscription (after only)
const unsubscribe = store.subscribeAction((action, state) => {
console.log("Action completed:", action.type);
});
// Cleanup
unsubscribeMutation();
unsubscribeAction();Watch API
// Watch specific state changes
const unwatch = store.watch(
(state, getters) => state.user, // Getter function
(newValue, oldValue) => {
// Callback
console.log("User changed from", oldValue, "to", newValue);
},
{
deep: true, // Deep watch objects
immediate: true, // Call immediately with current value
}
);
// Watch computed values
store.watch(
(state, getters) => getters.userInfo,
(newValue, oldValue) => {
console.log("User info updated:", newValue);
}
);
// Complex watched expressions
store.watch(
(state) => state.moduleA.count + state.moduleB.value,
(sum) => {
console.log("Combined value:", sum);
}
);
// Cleanup
unwatch();Type Definitions Reference
This section summarizes the key types and declaration points provided by the strict-vuex package so you know where to look when something doesn't type-check.
VuexStoreRootState/VuexStoreRootGetters/VuexStoreRootActions/VuexStoreRootMutations:- Root-level shape placeholders — add your root-level types here when you need global state/getters/actions/mutations typed.
VuexStoreRootModules:- The single most important interface to populate. Put only your root module types here (e.g.
{ moduleA: ModuleA; moduleB: ModuleB }). The file builds the entire tree from these root entries.
- The single most important interface to populate. Put only your root module types here (e.g.
_Module<ModuleName, Mode, State, Getters, Actions, Mutations, Modules>:- Use this generic to describe each module.
Modecontrols namespacing ('isolated'===namespaced: true,'default'===namespaced: false).
- Use this generic to describe each module.
StoreActionRecord<Payload, Return, RootLevel>:- Use to declare actions payload/return types and whether they are root-level (
RootLevel = true).
- Use to declare actions payload/return types and whether they are root-level (
ResolveModuleStateByName<ModuleName>:- get a module's full resolved state including nested children.
Some Examples:
StoreActionRecord - interface
interface StoreActionRecord<
Payload,
Return,
RootLevel extends boolean = false
> {
payload: Payload;
return: Return;
root: RootLevel;
}
// Usage
type MyAction = StoreActionRecord<
{ id: string; data: any }, // Payload type
Promise<boolean>, // Return type
false // Is root action
>;_Module - interface
interface _Module<
ModuleName extends ModuleNames,
Mode extends ModuleMode, // 'default' | 'isolated'
State extends StoreStateGeneric = {},
Getters extends StoreGettersGeneric = {},
Actions extends StoreActionsGeneric = {},
Mutations extends StoreMutationsGeneric = {},
Modules = undefined // Nested modules
>
// Full example
type CompleteModule = _Module<
'complete',
'isolated',
{
// State
counter: number;
data: string[];
settings: {
theme: 'light' | 'dark';
language: string;
};
},
{
// Getters
doubleCounter: number;
dataCount: number;
formattedData: string;
},
{
// Actions
increment: StoreActionRecord<{ amount: number }, void>;
fetchData: StoreActionRecord<null, string[]>;
updateSettings: StoreActionRecord<{ theme?: string; language?: string }, boolean>;
},
{
// Mutations
SET_COUNTER: { value: number };
ADD_DATA: { item: string };
UPDATE_SETTINGS: { theme?: string; language?: string };
},
{
// Nested modules
subModuleA: SubModuleA;
subModuleB: SubModuleB;
}
>;ResolveModuleStateByName - type
type StateB = ResolveModuleStateByName<"moduleB">;
// Result: { b: string, moduleC: { c: string } }
// Usage in components or actions:
const moduleState: ResolveModuleStateByName<"moduleB"> = // ...
console.log(moduleState.b); // string
console.log(moduleState.moduleC.c); // stringAPI Reference
Store Methods
| Method | Description | Type Signature | | ---------------- | --------------------------- | --------------------------------------------- | | state | Access store state | readonly RootState | | getters | Access store getters | readonly RootGetters | | dispatch | Dispatch actions | (type, payload?, options?) => Promise | | commit | Commit mutations | (type, payload?, options?) => void | | subscribe | Subscribe to mutations | (fn, options?) => () => void | | subscribeAction | Subscribe to actions | (fn, options?) => () => void | | watch | Watch reactive changes | (getter, cb, options?) => () => void | | registerModule | Register module dynamically | (path, module, options?) => void | | unregisterModule | Unregister module | (path) => void | | hasModule | Check module existence | (path) => boolean | | hotUpdate | Hot update modules | (options) => void | | replaceState | Replace entire state | (state) => void |
Mapper Functions
| Function | Description | Usage | | ----------------------- | -----------------
