Lead Instructor @ Vue School
Full Stack developer (10 years)
Husband and Father
844 Lessons | 70 Hours | 45 Courses
Eduardo uses
VS Code
.vscode/settings.json
Uses TypeScript
Pinia is a monorepo
docs built with Vitepress
includes a Nuxt module
the main package
Built in devtools support
Used to install Pinia with Vue
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
app.use(createPinia())
Used to install Pinia with Vue
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// rootStore.ts
/**
* Every application must own its own pinia to be able to create stores
*/
export interface Pinia {
install: (app: App) => void
/**
* root state
*/
state: Ref<Record<string, StateTree>>
/**
* Adds a store plugin to extend every store
*
* @param plugin - store plugin to add
*/
use(plugin: PiniaPlugin): Pinia
/**
* Installed store plugins
*
* @internal
*/
_p: PiniaPlugin[]
/**
* App linked to this Pinia instance
*
* @internal
*/
_a: App
/**
* Effect scope the pinia is attached to
*
* @internal
*/
_e: EffectScope
/**
* Registry of stores used by this pinia.
*
* @internal
*/
_s: Map<string, StoreGeneric>
/**
* Added by `createTestingPinia()` to bypass `useStore(pinia)`.
*
* @internal
*/
_testing?: boolean
}
// rootStore.ts
/**
* Every application must own its own pinia to be able to create stores
*/
export interface Pinia {
install: (app: App) => void
/**
* root state
*/
state: Ref<Record<string, StateTree>>
/**
* Adds a store plugin to extend every store
*
* @param plugin - store plugin to add
*/
use(plugin: PiniaPlugin): Pinia
/**
* Installed store plugins
*
* @internal
*/
_p: PiniaPlugin[]
/**
* App linked to this Pinia instance
*
* @internal
*/
_a: App
/**
* Effect scope the pinia is attached to
*
* @internal
*/
_e: EffectScope
/**
* Registry of stores used by this pinia.
*
* @internal
*/
_s: Map<string, StoreGeneric>
/**
* Added by `createTestingPinia()` to bypass `useStore(pinia)`.
*
* @internal
*/
_testing?: boolean
}
// rootStore.ts
/**
* Every application must own its own pinia to be able to create stores
*/
export interface Pinia {
install: (app: App) => void
/**
* root state
*/
state: Ref<Record<string, StateTree>>
/**
* Adds a store plugin to extend every store
*
* @param plugin - store plugin to add
*/
use(plugin: PiniaPlugin): Pinia
/**
* Installed store plugins
*
* @internal
*/
_p: PiniaPlugin[]
/**
* App linked to this Pinia instance
*
* @internal
*/
_a: App
/**
* Effect scope the pinia is attached to
*
* @internal
*/
_e: EffectScope
/**
* Registry of stores used by this pinia.
*
* @internal
*/
_s: Map<string, StoreGeneric>
/**
* Added by `createTestingPinia()` to bypass `useStore(pinia)`.
*
* @internal
*/
_testing?: boolean
}
// types.ts
export type StateTree = Record<string | number | symbol, any>
TS Record Utility Type
// rootStore.ts
/**
* Every application must own its own pinia to be able to create stores
*/
export interface Pinia {
install: (app: App) => void
/**
* root state
*/
state: Ref<Record<string, StateTree>>
/**
* Adds a store plugin to extend every store
*
* @param plugin - store plugin to add
*/
use(plugin: PiniaPlugin): Pinia
/**
* Installed store plugins
*
* @internal
*/
_p: PiniaPlugin[]
/**
* App linked to this Pinia instance
*
* @internal
*/
_a: App
/**
* Effect scope the pinia is attached to
*
* @internal
*/
_e: EffectScope
/**
* Registry of stores used by this pinia.
*
* @internal
*/
_s: Map<string, StoreGeneric>
/**
* Added by `createTestingPinia()` to bypass `useStore(pinia)`.
*
* @internal
*/
_testing?: boolean
}
export const piniaSymbol = (
__DEV__ ? Symbol('pinia') : /* istanbul ignore next */ Symbol()
) as InjectionKey<Pinia>
/**
* Context argument passed to Pinia plugins.
*/
export interface PiniaPluginContext<
Id extends string = string,
S extends StateTree = StateTree,
G /* extends _GettersTree<S> */ = _GettersTree<S>,
A /* extends _ActionsTree */ = _ActionsTree
> {
/**
* pinia instance.
*/
pinia: Pinia
/**
* Current app created with `Vue.createApp()`.
*/
app: App
/**
* Current store being extended.
*/
store: Store<Id, S, G, A>
/**
* Initial options defining the store when calling `defineStore()`.
*/
options: DefineStoreOptionsInPlugin<Id, S, G, A>
}
/**
* Plugin to extend every store.
*/
export interface PiniaPlugin {
/**
* Plugin to extend every store. Returns an object to extend the store or
* nothing.
*
* @param context - Context
*/
(context: PiniaPluginContext): Partial<
PiniaCustomProperties & PiniaCustomStateProperties
> | void
}
https://tsdoc.org
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
Handle Pinia Plugins
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// rootStore.ts
export let activePinia: Pinia | undefined
export const setActivePinia = (pinia: Pinia | undefined) =>
(activePinia = pinia)
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
// createPinia.ts
import { Pinia, PiniaPlugin, setActivePinia, piniaSymbol } from './rootStore'
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
*/
export function createPinia(): Pinia {
const scope = effectScope(true)
// NOTE: here we could check the window object for a state and directly set it
// if there is anything like it with Vue 3 SSR
const state = scope.run<Ref<Record<string, StateTree>>>(() =>
ref<Record<string, StateTree>>({})
)!
let _p: Pinia['_p'] = []
// plugins added before calling app.use(pinia)
let toBeInstalled: PiniaPlugin[] = []
const pinia: Pinia = markRaw({
install(app: App) {
// this allows calling useStore() outside of a component setup after
// installing pinia's plugin
setActivePinia(pinia)
if (!isVue2) {
pinia._a = app
app.provide(piniaSymbol, pinia)
app.config.globalProperties.$pinia = pinia
/* istanbul ignore else */
if (__DEV__ && IS_CLIENT) {
registerPiniaDevtools(app, pinia)
}
toBeInstalled.forEach((plugin) => _p.push(plugin))
toBeInstalled = []
}
},
use(plugin) {
if (!this._a && !isVue2) {
toBeInstalled.push(plugin)
} else {
_p.push(plugin)
}
return this
},
_p,
// it's actually undefined here
// @ts-expect-error
_a: null,
_e: scope,
_s: new Map<string, StoreGeneric>(),
state,
})
// pinia devtools rely on dev only features so they cannot be forced unless
// the dev build of Vue is used
// We also don't need devtools in test mode
if (__DEV__ && IS_CLIENT && !__TEST__) {
pinia.use(devtoolsPlugin)
}
return pinia
}
Key Takeaways
// couter.ts
import { defineStore, acceptHMRUpdate } from "pinia";
export const useCounter = defineStore("counter", {
state: ()=>{},
getters:{},
actions:{}
})
Use to create a store
// pinia/store.ts
/**
* Creates a `useStore` function that retrieves the store instance
*
* @param id - id of the store (must be unique)
* @param options - options to define the store
*/
export function defineStore(
id: Id,
options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
){}
/**
* Creates a `useStore` function that retrieves the store instance
*
* @param options - options to define the store
*/
export function defineStore(
options: DefineStoreOptions<Id, S, G, A>
){}
/**
* Creates a `useStore` function that retrieves the store instance
*
* @param id - id of the store (must be unique)
* @param storeSetup - function that defines the store
* @param options - extra options
*/
export function defineStore(
id: Id,
storeSetup: () => SS,
options?: DefineSetupStoreOptions</*...*/>
){}
TypeScript Overloads Allow for Flexible Function Parameters
// pinia/store.ts
export function defineStore(
// TODO: add proper types from above
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options //... some type stuff
const isSetupStore = typeof setup === 'function'
if (typeof idOrOptions === 'string') {
id = idOrOptions
// the option store setup will contain the actual options in this case
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// creates a setupStore or an options store
// handle some devtools stuff
}
useStore.$id = id
return useStore
}
defineStore accepts all params as any to be compatible with overloads
// pinia/store.ts
export function defineStore(
// TODO: add proper types from above
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options //... some type stuff
const isSetupStore = typeof setup === 'function'
if (typeof idOrOptions === 'string') {
id = idOrOptions
// the option store setup will contain the actual options in this case
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// creates a setupStore or an options store
// handle some devtools stuff
}
useStore.$id = id
return useStore
}
resolves the various ways of accepting parameters
// pinia/store.ts
export function defineStore(
// TODO: add proper types from above
idOrOptions: any,
setup?: any,
setupOptions?: any
): StoreDefinition {
let id: string
let options //... some type stuff
const isSetupStore = typeof setup === 'function'
if (typeof idOrOptions === 'string') {
id = idOrOptions
// the option store setup will contain the actual options in this case
options = isSetupStore ? setupOptions : setup
} else {
options = idOrOptions
id = idOrOptions.id
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
// creates a setupStore or an options store
// handle some devtools stuff
}
useStore.$id = id
return useStore
}
returns a useStore function
returns a useStore function
// component.vue
<script setup>
import {useCounter} from "@/stores/counter"
const counter = useCounter(); // this is calling useStore()
</script>
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
The state on the root store
initial state of the store set based on the state in the root store
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
createOptionsStore converts the options to a setup function
(Options stores are syntactic sugar on top of setup store)
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
state in the rootStore updated to the return from your state function
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
all your state converted to reactive refs
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
state, actions, getters all assigned as props on the returned object
(getters wrapped with CAPI computed function)
enables us to access everything with . on the store
// component.vue
<script setup>
import {useCounter} from "@/stores/counter"
const counter = useCounter();
counter.count // access state with .
counter.increment() // call actions with .
counter.doubled // access getters with .
</script>
pass the setup function to a createSetupStore function
(and define the $reset Pinia method)
// pinia/store.ts
function createOptionsStore<...
>(
id: Id,
options: DefineStoreOptions<Id, S, G, A>,
pinia: Pinia,
hot?: boolean
): Store<Id, S, G, A> {
const { state, actions, getters } = options
const initialState: StateTree | undefined = pinia.state.value[id]
let store: Store<Id, S, G, A>
function setup() {
if (!initialState && (!__DEV__ || !hot)) {
/* istanbul ignore if */
if (isVue2) {
set(pinia.state.value, id, state ? state() : {})
} else {
pinia.state.value[id] = state ? state() : {}
}
}
// avoid creating a state in pinia.state.value
const localState =
__DEV__ && hot
? // use ref() to unwrap refs inside state TODO: check if this is still necessary
toRefs(ref(state ? state() : {}).value)
: toRefs(pinia.state.value[id])
return assign(
localState,
actions,
Object.keys(getters || {}).reduce((computedGetters, name) => {
if (__DEV__ && name in localState) {
console.warn(
`[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".`
)
}
computedGetters[name] = markRaw(
computed(() => {
setActivePinia(pinia)
// it was created just before
const store = pinia._s.get(id)!
// allow cross using stores
/* istanbul ignore next */
if (isVue2 && !store._r) return
// @ts-expect-error
// return getters![name].call(context, context)
// TODO: avoid reading the getter while assigning with a global variable
return getters![name].call(store, store)
})
)
return computedGetters
}, {} as Record<string, ComputedRef>)
)
}
store = createSetupStore(id, setup, options, pinia, hot, true)
store.$reset = function $reset() {
const newState = state ? state() : {}
// we use a patch to group all changes into one single subscription
this.$patch(($state) => {
assign($state, newState)
})
}
return store as any
}
// pinia/store.ts
function createSetupStore{
// 1. provides some devtools support for pinia events
// 2. defines the $patch function
// 3. defines the $dispose function
// 4. runs the setup function assembled in createOptionsStore
// (or passed directly by developer if using setup store)
// 5. overwrites existing actions to support $onAction
// 6. defines the $state property to get all the state from the store at once
// 7. deals with HMR support
// 8. applies all the plugins
// 9. returns the state along with the $ functions
}
high level overview of createSetupStore
Key Takeaways
📺
Vue Video
Courses
👨🏫
Live Workshops
🧑🔧
Database for hiring
Vue devs
Vue.js Fundamentals
30 November 2022
Vue 3 Composition API
2 February 2023
Grab a FREE limited edition Vue School t-shirt