How to type this
in getters and actions defined outside a store?
#983
-
I am trying to improve the readability of my stores by breaking down lengthy getters and actions into their own separate files. Currently, the // cake.ts
import { restockCakes } from './actions/restockCakes'
export const useCakeStore = defineStore('cake', {
state: () => ({
cakesInStock: 5
}),
actions: {
restockCakes
}
}) // restockCakes.ts
export function restockCakes(
this: typeof import('../cake')['useCakeStore'],
quantity: number
) {
this.cakesInStock += quantity
} If I hover over I appreciate any help. |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 8 replies
-
It's probably not an answer to your question but an alternative way is to use the import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useStoreCounter = defineStore('counter', () => {
// STATES
const count = ref(0)
// GETTERS
const isEven = computed(() => {
return count.value % 2 === 0
})
const messageIfEven = computed(() => {
return (message) => {
if (!isEven.value) return
return message
}
})
// ACTIONS
function increment() {
count.value++
}
return { count, isEven, messageIfEven, increment }
}) |
Beta Was this translation helpful? Give feedback.
-
I'm struggling with the same issue and was about to make a new thread! I come from using Pinia in JavaScript and wanted to try it out on TypeScript, and I can't use "this" in an action function that is in a separate module.
I tried defining an interface for the store state, and casting "this" to that interface like |
Beta Was this translation helpful? Give feedback.
-
UpdateI think I might have found the solution? ExplanationI always forget that in TypeScript, we can use the first parameter of a function, to speficy a type for the |
Beta Was this translation helpful? Give feedback.
-
Update: Partially SolvedI've managed to break down my store getters and actions into separate files with full TypeScript intellisense and type-checking support. What's missing?I've had to manually type the store getters. This is a small annoyance compared to the "define everything on the same file" approach, where Pinia automatically infers the type of getters and lets you access them type-safe from other getters or actions. Using the If anyone knows how to improve this (ie: create a store with getters and actions defined in individual files without having to manually type all your getters), please let me know. In the meantime, manually typing my getters seems like a fair compromise for the increased neatness. My Partial SolutionFor anyone curious, here's a minimal demo of how to structure a store with getters and actions defined in separate files: Note: It could be hard to understand what's going on at first, but if you go through all the snippets and then come back to review the first three, it will all make sense. Demo Folder & File Structure:
Store// count.ts (store)
import type { CountStoreState, CountStoreGetters } from './types/count'
import { doubleCount } from './getters/doubleCount'
import { quadrupleCount } from './getters/quadrupleCount'
import { increaseCount } from './actions/increaseCount'
import { increaseCountTwofold } from './actions/increaseCountTwofold'
const state = (): CountStoreState => ({
count: 5
}) // (1)
const getters: CountStoreGetters = {
doubleCount,
quadrupleCount
} // (1)
const actions = {
increaseCount,
increaseCountTwofold
} // (2)
export const useCountStore = defineStore('count', {
state,
getters: { ...getters }, // (3)
actions
})
/*
NOTES:
(1): Specify the type of state and getters to ensure it matches our manually defined
types from ./types/count.ts (since they're independently defined, we need to do this
to prevent deviations.)
(2): No need to manually define or expect types for actions because the CountStore
type used by actions is dynamically derived from the store's computed type —see point (3)
in ./types/count.ts for context.
(3): Not sure why, but Pinia doesn't like to get a typed object as a value for getters (
setting getters like this: { state, getters, actions } will give an error.) Using the spread
operator is a handy hack to get rid of the types (without giving up on our previous type
safety check) before passing our getters to the store.
*/ Store Types// count.ts (types)
export interface CountStoreState {
count: number
}
export interface CountStoreGetters {
doubleCount: (state: CountStoreState) => number
quadrupleCount: (this: CountStoreStateAndGetters) => number
} // (1)
export type CountStoreComputedGetters = {
[Getter in keyof CountStoreGetters]: ReturnType<CountStoreGetters[Getter]>
} // (1)
export type CountStoreStateAndGetters = CountStoreState &
CountStoreComputedGetters // (2)
export type CountStore = ReturnType<typeof import('../count')['useCountStore']> // (3)
/*
NOTES:
(1): Define function signatures for getters (which will be used to ensure the getters
we pass to our store match the format defined here) and then generate a mapped type that
replaces the function signatures with their return types (used inside regular function
getters so that we can access a value like this.doubleCount as usual instead of being
expected to invoke it as this.doubleCount() first.)
(2): This will be the value we'll use to type `this` inside our getters. I don't know why,
but using the same trick from point (3) here would not work. Pinia supports actions where
`this` is typed using the computed store's type, but if you try to do the same thing for
getters it causes a recursion error (that's why we manually need to type our state and
getters, but don't need to do the same for actions.)
(3): Dynamically import the computed type of our store. We will use this to type `this`
inside our externally defined actions.
*/ GettersUsing arrow functions:import type { CountStoreState } from '../types/count'
export const doubleCount = (state: CountStoreState): number => state.count * 2 Accessing other getters:import type { CountStoreStateAndGetters } from '../types/count'
export function quadrupleCount(this: CountStoreStateAndGetters): number {
return this.doubleCount * 2
} ActionsTaking a payload:// increaseCount.ts
import type { CountStore } from '../types/count'
export function increaseCount(this: CountStore, increment: number): void {
this.count += increment
} Invoking other actions:// increaseCountTwofold.ts
import type { CountStore } from '../types/count'
export function increaseCountTwofold(this: CountStore): void {
this.increaseCount(this.count)
} Hopefully this was useful. |
Beta Was this translation helpful? Give feedback.
-
This works fine for me. You will also get type reference for your inside your action.
|
Beta Was this translation helpful? Give feedback.
-
If anyone is interested in using a CompositionAPI-alike syntax, Pinia's defineStore accepts a function instead of an object: export const useCountStore = defineStore('count', () => {
const count: Ref<number> = ref<number>(0);
function increase(): void {
count.value += 1;
};
return {
count,
increase,
}
}); |
Beta Was this translation helpful? Give feedback.
-
How about this? // state.ts
import {ref} from 'vue'
export const count = ref(0) // getters.ts
import {computed} from 'vue'
import {count} from './state'
export const isEven = computed(() => count.value % 2 === 0)
export const messageIfEven = computed(() => {
return message => {
if (!isEven.value) return
return message
}
}) // actions.ts
import {count} from './state'
export const increment = () => count.value++ // index.ts
import {defineStore} from 'pinia'
import * as state from './state'
import * as getters from './getters'
import * as actions from './actions'
export const useStoreCounter = defineStore('counter', () => ({
...state,
...getters,
...actions,
})
) |
Beta Was this translation helpful? Give feedback.
Update: Partially Solved
I've managed to break down my store getters and actions into separate files with full TypeScript intellisense and type-checking support.
What's missing?
I've had to manually type the store getters. This is a small annoyance compared to the "define everything on the same file" approach, where Pinia automatically infers the type of getters and lets you access them type-safe from other getters or actions.
Using the
ReturnType<typeof import('path/to/myStore.ts)['useMyStore']>
syntax to automatically create a type with the store's computed type works wonders foractions
but, unfortunately, does not work forgetters
(it causes a circular error). I don't know why it work…