Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): Add optional stronger typings #2053

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kadet1090
Copy link

@kadet1090 kadet1090 commented Sep 12, 2021

This PR will include types from my vuex-strong project into the core. For now I consider this pull request work in progress but it is quite feature complete and I won't be able to achieve much more without feedback.

Fixes #1831

Rationale

Vuex includes types for typescript with official releases. This types are really loose and they do only basic type checking, with some things being just impossible to verify by the typescript compiler - for example payloads are defined always as any. Vuex utilizes dynamic nature of JS and until typescript 4.1, which introduced template literal types that allows modules to be correctly handled.

Important design decisions

There are few important design decisions that were made, but most of them could be easily changed if needed. This types were made from scratch and are not compatible with current ones. I personally don't think that it's possible to create types that are compatible with previous ones. As this types are not compatible a lot more strict than current ones, they should be optional. For now I think that it will be best to make them opt-in so anyone can use it with simple alternation tsconfig.json.

By convention I prefixed all exported types with Vuex, so instead of Module there is VuexModule etc. This was done to reduce confusion and possible collisions with names when importing. I don't think that this is necessary and it's possible to remove these prefixes, but in my opinion they make things more clear in the code as it's clear what is part of vuex and what is not.

Usage

The core idea for this set of types can be simply described as type first. You should start designing your store from contract, and your store should implement this contract - not the other way around. As from this package point of view the store is just a vuex module with extra stuff, and I will use word store to refer to both store and module.

For example, simple counter store could be described as follows:

type CounterState = { value: number }
enum CounterMutations { Increment = "increment", Decrement = "decrement" }

type CounterMutationsTree = {
  [CounterMutations.Increment]: VuexMutationHandler<CounterState, number>, // number is payload
  [CounterMutations.Decrement]: VuexMutationHandler<CounterState, number>,
}

// or you could write it like below, both syntaxes are equally valid
type CounterMutationsTree = {
  [CounterMutations.Increment](state: CounterState, payload: number): void,
  [CounterMutations.Decrement](state: CounterState, payload: number): void,
}

type CounterModule = VuexGlobalModule<CounterState, CounterMutationsTree>
// or
type CounterModule = {
  state: CounterState,
  mutations: CounterMutationsTree
}

export const counter: CounterModule = { 
  ... // will enforce proper types
}

Modules - VuexModule

Modules are arguably the most important aspect of vuex, the store itself can be thought of as main module with some extra configurations - and this is in fact how store definition is typed by this project

export type VuexModule<
  TState extends {},
  TMutations extends VuexMutationsTree,
  TActions extends VuexActionsTree,
  TGetters extends VuexGettersTree,
  TModules extends VuexModulesTree
> = GlobalVuexModule<TState, TMutations, TActions, TGetters, TModules>
  | NamespacedVuexModule<TState, TMutations, TActions, TGetters, TModules>

Namespaced modules differs in behavior from non-namespaced (global) modules - therefore there are in fact two different types: GlobalVuexModule and NamespacedVuexModule.

export type NamespacedVuexModule<
  TState extends {} = {},
  TMutations extends VuexMutationsTree<TState> = VuexMutationsTree<TState>,
  TActions extends VuexActionsTree<NamespacedVuexModule<TState, TMutations, TActions, TGetters, TModules>> = {} | undefined,
  TGetters extends VuexGettersTree = {} | undefined,
  TModules extends VuexModulesTree = {} | undefined,
> = BaseVuexModule<TState, TMutations, TActions, TGetters, TModules> 
  & { namespaced: true } // Namespaced modules require to have namespaced option set to true

export type GlobalVuexModule<
  TState extends {} = {},
  TMutations extends VuexMutationsTree<TState> = VuexMutationsTree<TState>,
  TActions extends VuexActionsTree<GlobalVuexModule<TState, TMutations, TActions, TGetters, TModules>> = {} | undefined,
  TGetters extends VuexGettersTree = {} | undefined,
  TModules extends VuexModulesTree = {} | undefined,
> = BaseVuexModule<TState, TMutations, TActions, TGetters, TModules> 
  & { namespaced?: false } // Global modules can either have no namespaced property or have it set as false

We can use both types to properly define required module contract

type FooModule = NamespacedVuexModule<FooState, FooMutationsTree, FooActionsTree, FooGettersTree, { sub: BazModule }>

// only used properties need to be set, others can be safely set to undefined
type BarModule = GlobalVuexModule<BarState, BarMutationsTree>
type FizzModule = GlobalVuexModule<FizzState, FizzMutationsTree, undefined, FizzGettersTree>

// you can also use plain "object" type syntax if you prefer
// this will work but won't tell you immediately that your contract is incompatible
type BarModule = {
  state: VuexStateProvider<BarState>,
  mutations: BarMutationsTree
}

// now you can just implement your contract, and typescript will aid you
const foo: FooModule = { /* ... */ }

Modules can have sub-modules, that are described by the VuexModulesTree - basically every module can be sub-module of any other module as long as it does not create circular reference.

export type VuexModulesTree 
  = { [name: string]: VuexModule }

All properties like mutations, actions and getters will be correctly namespaced based on the sub-module kind.

State

State is the most straightforward aspect of module - it just represents data held in the store and basically can be any of type.

Full state

However, full state also includes state of the modules, full state of the module can be obtained using VuexState<TModule>

type SubModule  = NamespacedVuexModule<{ inner: number }, /* ... */>
type RootModule = {
  state: VuexStateProvider<{ root: string }>,
  /* ... */,
  modules: {
    sub:
  }
}

VuexState<RootModule> == { 
  root: string,
  sub: {
    inner: number
  }
}

State provider

State can be provider by value or by factory, therefore we require to type that accordingly. To simplify things,VuexStateProvider<TState> helper can be utilized:

export type VuexStateProvider<TState>
  = TState
  | (() => TState)

To extract state from the provider, VuexExtractState<TProvider> can be used:

VuexExtractState<VuexStateProvider<FooState>> == FooState

Mutations

Of course the state have to change somehow - that's what mutations do. All mutations available in given module are described by the VuexMutationsTree type. Mutation tree is just an keyed storage for mutation handlers described by VuexMutationHandler:

export type VuexMutationsTree<TState = any, TDefinition extends VuexStoreDefinition = any>
  = { [name: string]: VuexMutationHandler<TState, any, TDefinition>; }

export type VuexMutationHandler<
  TState, 
  TPayload = never, 
  TDefinition extends VuexStoreDefinition = any,
>

Example of mutation tree definition:

type FooMutations = {
  added: VuexMutation<FooState, string>,
  removed: VuexMutation<FooState, number>,
}
// or
type FooMutations = {
  added(state: FooState, payload: string): void;
  removed(state: FooState, payload: number): void;
}

If mutation requires access to the whole store, it can be typed using third generic argument of VuexMutationHandler:

VuexMutation<FooState, string, StoreDefinition>
// or
(this: VuexStore<StoreDefinition>, state: FooState, payload: string): void;

Be careful when using this construct though, it is circular reference and can create infinite recursion in typescript if not used with caution.

It is common (and in my opinion recommended) for mutation types to be defined as code constants - and in typescript it's possible to use enums for that purpose:

enum FooMutations {
  Added = "added",
  Removed = "removed",
}

Then values of this enum can be used to key mutations tree type:

type FooMutationTree = {
  [FooMutations.Added]: VuexMutationHandler<FooState, string>
  [FooMutations.Removed]: VuexMutationHandler<FooState, number>
}

Implementation of mutations can be done simply by implementing the tree:

// should ensure that everything is typed correctly
const mutations: FooMutationTree = { /* ... */ }

Such well-defined mutations are then available from commit method of the store:

type MyStore = {
  state: { /* ... */ },
  modules: {
    foo: FooModule
  }
}

let store = createStore<MyStore>(/* ... */)

// should check types and provide code assistance
store.commit("foo/added", "test");
store.commit({ type: "foo/added", payload: "test" });

There also exists few utility types:

  • VuexMutationTypes<TModule> - defines all possible mutation types, e.g. VuexMutationTypes<MyStore> = "foo/added" | "foo/removed" | /* ... */
  • VuexMutations<TModule> - defines all possible mutations with payloads, e.g. VuexMutations<MyStore> = { type: "foo/added", payload: string } | { type: foo/removed", payload: number } | /* ... */
  • VuexMutationPayload<TModule, TMutation> - extracts mutation payload by name, e.g. VuexMutationPayload<MyStore, "foo/added"> = string

Actions

In principle, actions are very similar to mutations - the main difference is that they can be asynchronous and have return types. Actions make changes in the state by committing mutations, they can also dispatch another actions if needed. Oh, and in case of namespaced modules they are scoped to module.

Action tree is described by the VuexActionsTree type, which itself (same as mutations tree case) is just an collection of action handles:

export type VuexActionsTree<
  TModule extends VuexModule = any, 
  TDefinition extends VuexStoreDefinition = any
> = { [name: string]: VuexActionHandler<TModule, any, any, TDefinition>; }

export type VuexActionHandler<
  TModule extends VuexModule, 
  TPayload = never, 
  TResult = Promise<void>,
  TDefinition extends VuexStoreDefinition = any,
>

Because actions can access basically everything from the module itself they need to have module back referenced - this can be a little bit tricky sometimes and can cause infinite recursion if not used with caution. However it should be fine in most cases.

Let's reiterate on the FooModule example to better see how actions can be defined:

enum FooMutations {
  Added = "added",
  Removed = "removed",
}

enum FooActions {
  Refresh = "refresh",
  Load = "load",
}

type FooState = { list: string[] }

type FooMutationTree = {
  [FooMutations.Added]: VuexMutationHandler<FooState, string, MyStore>
  [FooMutations.Removed]: VuexMutationHandler<FooState, number, MyStore>
}

type FooActionsTree = {
  // FooActions.Refresh is scoped to FooModule, does not need any payload, returns Promise<void> and is part of MyStore compatible store
  [FooActions.Refresh]: VuexActionHandler<FooModule, never, Promise<void>, MyStore>,
  // FooActions.Load is scoped to FooModule, does require array of strings as payload, returns Promise<string[]> and is part of MyStore compatible store
  [FooActions.Load]: VuexActionHandler<FooModule, string[], Promise<string[]>, MyStore>,
}

type FooModule = NamespacedVuexModule<FooState, FooMutationTree, FooActionsTree>

Of course just like in mutations it is possible to use alternative function based syntax for handler definition - you will however need to make it compatible with requirements by yourself. Enums are also not required but I will stick with them in the rest of examples as I personally think that this is the most correct way.

And again, just like mutations implementation can be done simply by implementing created action tree type:

const actions: FooActionsTree = { /* ... */ }

Context (defined by VuexActionContext<TModule, TRoot> type) that is passed to action handler should be typed correctly and scoped to passed module:

const actions: FooActionsTree = {
  async load(context, payload): Promise<string[]> {
    // context is bound to this module
    // and payload is properly typed!
    context.commit(FooMutations.Added, payload[0]);

    // also works for actions
    context.dispatch(FooActions.Load, payload);
    context.dispatch(FooActions.Refresh);

    const list = context.state.list;

    // we can however access root state
    const bar = context.rootState.bar; // typeof bar = BarState;

    // ... and getters
    const first = context.rootGetters['anotherFoo/first'];

    return [];
  },
  async refresh(context) {
    // simple actions to not require return type!
  }
}

Again there also exists few utility types:

  • VuexActionTypes<TModule> - defines all possible action types, e.g. VuexActionTypes<MyStore> = "foo/load" | "foo/refresh" | /* ... */
  • VuexActions<TModule> - defines all possible actions with payloads, e.g. VuexActions<MyStore> = { type: "foo/load", payload: string[] } | { type: "foo/refresh" } | /* ... */
  • VuexActionPayload<TModule, TAction> - extracts action payload by name, e.g. VuexMutationPayload<MyStore, "foo/load"> = string[]
  • VuexActionResult<TModule, TAction> - extracts action result by name, e.g. VuexMutationPayload<MyStore, "foo/load"> = Promise<string[]>

Getters

Getters are, to put simply, just computer properties of state accessible by some key. Getters tree is described by the VuexGettersTree type, which itself is just an collection of getters:

export type VuexGettersTree<TModule extends VuexModule = any>
  = { [name: string]: VuexGetter<TModule, any, any, any>; }

export type VuexGetter<
  TModule extends VuexModule, 
  TResult, 
  TRoot extends VuexModule = any,
  TGetters = VuexGetters<TModule>
> 

Definition and implementation of getter tree is simple:

type FooGettersTree = {
  first: VuexGetter<FooModule, string>
  firstCapitalized: VuexGetter<FooModule, string>,
  rooted: VuexGetter<FooModule, string, MyStore>, // allows referencing root module
}

const getters: FooGettersTree = {
  first: state => state.list[0], // state is correctly typed
  firstCapitalized: (_, getters) => getters.first.toUpperCase(), // getters too!
  rooted: (state, getters, rootState, rootGetters) => rootState.global + rootGetters.globalGetter, // and global state!
}

And getters can be then accessed from the store, and the result will have correct type:

store.getters['foo/first'] // string

As always there exists few utility types:

  • VuexGetterNames<TModule> - defines all possible getter names, e.g. VuexGetterNames<MyStore> = "foo/first" | "foo/firstCapitalized" | /* ... */
  • VuexGetters<TModule> - defines all possible getters with results, e.g. VuexGetters<MyStore> = { first: string, firstCapitalized: string }

Store

As was previously said, the store definition is just a global module with extra properties:

export type VuexStoreDefinition<
  TState extends {} = {},
  TMutations extends VuexMutationsTree = VuexMutationsTree,
  TActions extends VuexActionsTree = VuexActionsTree,
  TGetters extends VuexGettersTree = VuexGettersTree,
  TModules extends VuexModulesTree = VuexModulesTree,
> = Omit<GlobalVuexModule<TState, TMutations, TActions, TGetters, TModules>, "namespaced">
  & {
    strict?: boolean,
    devtools?: boolean,
    plugins?: VuexPlugin<VuexStoreDefinition<TState, TMutations, TActions, TGetters, TModules>>[]
  }

The createStore function takes VuexStoreDefinition as an argument, and creates store instance from it. The store instance is described by the VuexStore<TDefinition extends VuexStoreDefinition> type.

Store instance should be fully typed and be mostly type-safe. It means that payloads of actions and mutations would be checked, action results will be known and could be checked, values from getters will be properly typed and so on. You also won't be able to commit/dispatch non-existent mutations/actions.

type MyStore = {
  state: {
    global: string;
  },
  modules: {
    foo: FooModule,
    bar: BarModule,
    anotherFoo: FooModule,
  },
}

// test
let store = createStore<MyStore>({ /* ... */ })

// should check and auto complete
store.commit("foo/added", "test");
store.commit({ type: "foo/added", payload: "test" });

// @ts-expect-error
store.commit("foo/added", 9);
// @ts-expect-error
store.commit("foo/added");

// dispatch works too!
store.dispatch("anotherFoo/load", ["test"]);
store.dispatch({ type: "anotherFoo/load", payload: ["test"] });

// @ts-expect-error
store.dispatch("anotherFoo/load", 0);
// @ts-expect-error
store.dispatch("foo/load");

// should check correctly
store.replaceState({
  global: "test",
  foo: {
    list: [],
    sub: {
      current: 0
    }
  },
  anotherFoo: {
    list: [],
    sub: {
      current: 0
    }
  },
  bar: {
    result: "fizzbuzz"
  }
})

// getters also work
store.getters['anotherFoo/first'];

// watch state is properly typed
store.watch(state => state.global, (value, oldValue) => value.toLowerCase() !== oldValue.toLowerCase())

// watch getters too!
store.watch((_, getters) => getters['foo/first'], (value, oldValue) => value.toLowerCase() !== oldValue.toLowerCase())

store.subscribe(mutation => {
  // properly detects payload type based on mutaiton kind
  if (mutation.type === "anotherFoo/sub/dec") {
    const number = mutation.payload; // typeof number = number
  } else if (mutation.type === "anotherFoo/added") {
    const str = mutation.payload; // typeof str = string
  }
})

store.subscribeAction((action, state) => {
  // properly detects payload type based on action kind
  if (action.type === "anotherFoo/load") {
    const arr = action.payload; // typeof arr = string[]
  }

  // state is also correctly represented
  const foo = state.foo.list;
})

// object notation is also supported
store.subscribeAction({
  after(action, state) { /* ... */ },
  before(action, state) { /* ... */ },
  error(action, state, error) { /* ... */ }
})

In theory store definition could be inferred from the argument of createStore but it's highly unrecommended as the contract will be basically guessed (which means that it can be guessed wrongly) based on definition and not checked - it should provide some useful features when using store instance though.

const store = createStore({
  state: { list: [] },
  mutations: {
    add(state, element: string) {
      // State won't be type-safe as full definition of this store is unknown at this point
      /* ... */ 
    },
    remove(state, index: number) { /* ... */ },
  },
  mutations: {
    clear(context) { 
      // Context won't be type-safe as full definition of this store is unknown at this point
      /* ... */ 
    },
    load(state, ids: string[]) { /* ... */ },
  }
})

// This should however work fine
store.commit('add', 'string')
store.commit('add', []) // should be an error as payload must be a string

// Same thing goes for dispatch
store.dispatch('clear')
store.load('load', ['x', 'y', 'z'])
store.load('load', 0) // should be an error as payload must be an array of strings

It is also possible to turn off type safety by explicitly providing any type to createStore, which could be useful when dealing with highly dynamic stores.

Component Binding Helpers

Vuex provides handy Component Binding Helpers that can be used for easily mapping state, getters, mutations and actions into component. Those helpers are also strictly typed. Unfortunately, until typescript has proper partial infering support (see issue #10571) it's not possible to use syntax like mapState<MyStore>(...). By default helpers are bound non-type-safely to any module, and to enable type safety it's required to re-export them with proper type applied. This could be done in the same file as store definition, for example:

import { 
  mapState as mapStateLoosely, 
  mapGetters as mapGettersLoosely, 
  mapMutations as mapMutationsLoosely, 
  mapActions as mapActionsLoosely, 
  createNamespacedHelpers as createLooseNamespacedHelpers,
  VuexMapStateHelper,
  VuexMapGettersHelper,
  VuexMapMutationsHelper,
  VuexMapActionsHelper, 
  VuexCreateNamespacedHelpers
} from "vuex";

type MyStore = { ... }

// as any step is required so typescript does not try to check type 
// compatibility, which can cause infinite loop in compiler
export const mapState = mapStateLoosely as any as VuexMapStateHelper<MyStore>;
export const mapGetters = mapGettersLoosely as any as VuexMapGettersHelper<MyStore>;
export const mapMutations = mapMutationsLoosely as any as VuexMapMutationsHelper<MyStore>;
export const mapActions = mapActionsLoosely as any as VuexMapActionsHelper<MyStore>;
export const createNamespacedHelpers = createLooseNamespacedHelpers as any as VuexCreateNamespacedHelpers<MyStore>;

Then you should be able to import those wrappers with typing applied and use type-safe helpers.

Gotchas and caveats

  1. This types are quite complex, errors can be overwhelming and hard to understand.
  2. This types are complex and occasionally you can face some bugs in the compiler that will cause it to stop responding and loop infinitely.

Full Example

This example is taken from the test/basic.ts file and could be interactively tested using vscode or other editor with decent support of typescript language server.

// example store definition
type FooState = { list: string[] }
type BarState = { result: string }
type BazState = { current: number }

enum FooMutations {
  Added = "added",
  Removed = "removed",
}

enum FooActions {
  Refresh = "refresh",
  Load = "load",
}

enum BarMutations {
  Fizz = "fizz",
  Buzz = "buzz",
}

enum BazMutations {
  Inc = "inc",
  Dec = "dec",
}

type FooMutationTree = {
  [FooMutations.Added]: VuexMutationHandler<FooState, string>
  [FooMutations.Removed]: VuexMutationHandler<FooState, number>
}

type FooActionsTree = {
  [FooActions.Refresh]: VuexActionHandler<FooModule, never, Promise<void>, MyStore>,
  [FooActions.Load]: VuexActionHandler<FooModule, string[], Promise<string[]>, MyStore>,
}

type FooGettersTree = {
  first: VuexGetter<FooModule, string>,
  firstCapitalized: VuexGetter<FooModule, string>,
  rooted: VuexGetter<FooModule, string, MyStore>,
}

type BarMutationTree = {
  [BarMutations.Fizz]: VuexMutationHandler<BarState, number>;
  [BarMutations.Buzz]: VuexMutationHandler<BarState>;
}

type BazMutationTree = {
  [BazMutations.Inc]: VuexMutationHandler<BazState, number>;
  [BazMutations.Dec]: VuexMutationHandler<BazState, number>;
}

type FooModule = NamespacedVuexModule<FooState, FooMutationTree, FooActionsTree, FooGettersTree, { sub: BazModule }>;
type BarModule = GlobalVuexModule<BarState, BarMutationTree>;
type BazModule = NamespacedVuexModule<BazState, BazMutationTree>;

type MyStore = {
  state: {
    global: string;
  },
  modules: {
    foo: FooModule,
    bar: BarModule,
    anotherFoo: FooModule,
  },
  mutations: {},
  getters: {},
  actions: {},
}

let store = createStore<MyStore>({} as any)

// should check and auto complete
store.commit("foo/added", "test");
store.commit({ type: "foo/added", payload: "test" });

// dispatch works too!
store.dispatch("anotherFoo/load", ["test"]);
store.dispatch({ type: "anotherFoo/load", payload: ["test"] });

// should check correctly
store.replaceState({
  global: "test",
  foo: {
    list: [],
    sub: {
      current: 0
    }
  },
  anotherFoo: {
    list: [],
    sub: {
      current: 0
    }
  },
  bar: {
    result: "fizzbuzz"
  }
})

// getters also work
store.getters['anotherFoo/first'];

// watch state is properly typed
store.watch(state => state.global, (value, oldValue) => value.toLowerCase() !== oldValue.toLowerCase())

// watch getters too!
store.watch((_, getters) => getters['foo/first'], (value, oldValue) => value.toLowerCase() !== oldValue.toLowerCase())

store.subscribe(mutation => {
  // properly detects payload type based on mutaiton kind
  if (mutation.type === "anotherFoo/sub/dec") {
    const number = mutation.payload; // typeof number = number
  } else if (mutation.type === "anotherFoo/added") {
    const str = mutation.payload; // typeof str = string
  }
})

store.subscribeAction((action, state) => {
  // properly detects payload type based on action kind
  if (action.type === "anotherFoo/load") {
    const arr = action.payload; // typeof arr = string[]
  }

  // state is also correctly represented
  const foo = state.foo.list;
})

// 
store.subscribeAction({
  after(action, state) { /* ... */ },
  before(action, state) { /* ... */ },
  error(action, state, error) { /* ... */ }
})

// getters with backreference
let fooGetters: FooGettersTree = {
  first: state => state.list[0], // state is correctly typed
  firstCapitalized: (_, getters) => getters.first.toUpperCase(), // getters too!
  rooted: (_, __, rootState, rootGetters) => rootState.global + rootGetters.globalGetter, // and global state!
}

let fooActions: FooActionsTree = {
  async load(context, payload): Promise<string[]> {
    // context is bound to this module
    // and payload is properly typed!
    context.commit(FooMutations.Added, payload[0]);

    context.dispatch(FooActions.Load, payload);
    context.dispatch(FooActions.Refresh);

    const list = context.state.list;

    // we can however access root state
    const bar = context.rootState.bar; // typeof bar = BarState;

    // ... and getters
    const first = context.rootGetters['anotherFoo/first'];

    return [];
  },
  async refresh(context) {
    // simple actions do not require return type!
  }
}

// utility types
type PayloadOfFooAddedMutation = VuexMutationPayload<MyStore, "foo/added">; // string

type PayloadOfFooLoadAction = VuexActionPayload<MyStore, "foo/load">; // string[]
type ResultOfFooLoadAction = VuexActionResult<MyStore, "foo/load">; // string[]

Progress

  • Modules
    • Global VuexGlobalModule<TState, TMutations = {}, TActions = {}, TModules = {}, TGetters = {}>
    • Namespaced VuexNamespacedModule<TState, TMutations = {}, TActions = {}, TModules = {}, TGetters = {}>
    • State TState
      • State helper VuexState<TModule>
        • Own VuexOwnState<TModule>
        • From submodules
    • Mutations TMutations extends VuexMutationsTree
      • Non-type-safe fallback VuexMutationsTree ??
      • Available mutations VuexMutations<TModule>
        • Own VuexOwnMutations<TModule>
        • From submodules
          • Global
          • Namespaced
      • Mutation handler type VuexMutationHandler<TState, TPayload = never, TStore = never>
        • Properly type this in handler (store backref)
      • Commit types
        • Payload VuexMutationPayload<TModule, TMutation>
        • Argument-Style VuexCommit<TModule>
        • Object-Style VuexMutations<TModule>
        • Commit options VuexCommitOptions
          • Support { root: true }
    • Actions TActions extends VuexActionsTree
      • Non-type-safe fallback VuexActionsTree ??
      • Available actions VuexActions<TModule>
        • Own VuexOwnActions<TModule>
        • From submodules
          • Global
          • Namespaced
      • Action handler VuexActionHandler<TModule, TPayload = never, TResult = Promise<void>>
        • Action Context VuexActionContext<TModule, TStoreDefinition = any>
        • Properly type this in handler (store backref)
      • Dispatch type VuexDispatch<TModule>
        • Payload VuexActionPayload<TModule, TAction>
        • Result VuexActionResult<TModule, TAction>
        • Argument-Style
        • Object-Style VuexAction<TModule>
        • Dispatch Options VuexDispatchOptions
          • Support { root: true }
    • Getters TGetters extends VuexGettersTree
      • Non-type-safe fallback VuexGettersTree ??
      • Available getters VuexGetters<TModule>
        • Own VuexOwnGetters<TModule>
        • From submodules
          • Global
          • Namespaced
      • Getter type VuexGetter<TModule, TResult>
        • Support for beckreferencing getters
      • Result VuexGetterResult<TModule, TGetter>
    • Submodules TModules extends VuexModulesTree
  • Store Definition VuexStoreDefinition<TState, TMutations = {}, TActions = {}, TModules = {}, TGetters = {}>
    • Basically VuexGlobalModule with additional things
    • Plugins VuexPlugin<TStoreDefinition>
    • Simple properties (devtools, etc.)
  • Store instance VuexStore<TStoreDefinition>
    • Constructor
      • Store Options VuexStoreDefinition
    • State (as defined by TStoreDefinition)
      • Replace state replaceState
    • Getters (as defined by TStoreDefinition)
    • Commit (as defined by TStoreDefinition)
    • Dispatch (as defined by TStoreDefinition)
    • Subscribers
      • Options VuexSubscribeOptions
      • Actions subscribeAction
        • Subscriber VuexActionSubscriber<TDefinition>
          • Callback VuexActionSubscriberCallback<TDefinition>
          • ErrorCallback VuexActionErrorSubscriberCallback<TDefinition>
          • Object VuexActionSubscribersObject<TDefinition>
      • Mutations subscribe
        • Subscriber VuexMutationSubscriber<TDefinition>
    • Watch watch
      • Options WatchOptions should be imported from Vue
    • Dynamic module management
      • Registration registerModule
      • Unregistration unregisterModule
      • Presence check hasModule
    • Hot Update - it's not type safe so it's declared loosely
    • Composition api helpers
      • useStore<TKey extends VuexInjectionKey<TStore>, TStore>(key: TKey): VuexStore<TStore>
    • Helpers
      • mapState<TModule> in form of VuexMapStateHelper<TModule>
      • mapMutations<TModule> in form of VuexMapMutationsHelper<TModule>
      • mapGetters<TModule> in form of VuexMapGettersHelper<TModule>
      • mapActions<TModule> in form of VuexMapActionsHelper<TModule>
      • createNamespaceHelpers<TModule> in form of VuexCreateNamespacedHelpers<TModule>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant