format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
This commit is contained in:
@@ -3,7 +3,7 @@ name: pinia
|
||||
description: Pinia official Vue state management library, type-safe and extensible. Use when defining stores, working with state/getters/actions, or implementing store patterns in Vue apps.
|
||||
metadata:
|
||||
author: Anthony Fu
|
||||
version: "2026.1.28"
|
||||
version: '2026.1.28'
|
||||
source: Generated from https://github.com/vuejs/pinia, scripts located at https://github.com/antfu/skills
|
||||
---
|
||||
|
||||
@@ -15,39 +15,39 @@ Pinia is the official state management library for Vue, designed to be intuitive
|
||||
|
||||
## Core References
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Topic | Description | Reference |
|
||||
| ------ | -------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| Stores | Defining stores, state, getters, actions, storeToRefs, subscriptions | [core-stores](references/core-stores.md) |
|
||||
|
||||
## Features
|
||||
|
||||
### Extensibility
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Topic | Description | Reference |
|
||||
| ------- | --------------------------------------------------------- | -------------------------------------------------- |
|
||||
| Plugins | Extend stores with custom properties, state, and behavior | [features-plugins](references/features-plugins.md) |
|
||||
|
||||
### Composability
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Composables | Using Vue composables within stores (VueUse, etc.) | [features-composables](references/features-composables.md) |
|
||||
| Topic | Description | Reference |
|
||||
| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| Composables | Using Vue composables within stores (VueUse, etc.) | [features-composables](references/features-composables.md) |
|
||||
| Composing Stores | Store-to-store communication, avoiding circular dependencies | [features-composing-stores](references/features-composing-stores.md) |
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| Testing | Unit testing with @pinia/testing, mocking, stubbing | [best-practices-testing](references/best-practices-testing.md) |
|
||||
| Topic | Description | Reference |
|
||||
| ------------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| Testing | Unit testing with @pinia/testing, mocking, stubbing | [best-practices-testing](references/best-practices-testing.md) |
|
||||
| Outside Components | Using stores in navigation guards, plugins, middlewares | [best-practices-outside-component](references/best-practices-outside-component.md) |
|
||||
|
||||
## Advanced
|
||||
|
||||
| Topic | Description | Reference |
|
||||
|-------|-------------|-----------|
|
||||
| SSR | Server-side rendering, state hydration | [advanced-ssr](references/advanced-ssr.md) |
|
||||
| Nuxt | Nuxt integration, auto-imports, SSR best practices | [advanced-nuxt](references/advanced-nuxt.md) |
|
||||
| HMR | Hot module replacement for development | [advanced-hmr](references/advanced-hmr.md) |
|
||||
| Topic | Description | Reference |
|
||||
| ----- | -------------------------------------------------- | -------------------------------------------- |
|
||||
| SSR | Server-side rendering, state hydration | [advanced-ssr](references/advanced-ssr.md) |
|
||||
| Nuxt | Nuxt integration, auto-imports, SSR best practices | [advanced-nuxt](references/advanced-nuxt.md) |
|
||||
| HMR | Hot module replacement for development | [advanced-hmr](references/advanced-hmr.md) |
|
||||
|
||||
## Key Recommendations
|
||||
|
||||
|
||||
@@ -12,30 +12,30 @@ Pinia supports HMR to edit stores without page reload, preserving existing state
|
||||
Add this snippet after each store definition:
|
||||
|
||||
```ts
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||
|
||||
export const useAuth = defineStore('auth', {
|
||||
// store options...
|
||||
})
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
|
||||
import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot));
|
||||
}
|
||||
```
|
||||
|
||||
## Setup Store Example
|
||||
|
||||
```ts
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const increment = () => count.value++
|
||||
return { count, increment }
|
||||
})
|
||||
const count = ref(0);
|
||||
const increment = () => count.value++;
|
||||
return { count, increment };
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
|
||||
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ npx nuxi@latest module add pinia
|
||||
This installs both `@pinia/nuxt` and `pinia`. If `pinia` isn't installed, add it manually.
|
||||
|
||||
> **npm users:** If you get `ERESOLVE unable to resolve dependency tree`, add to `package.json`:
|
||||
>
|
||||
> ```json
|
||||
> "overrides": { "vue": "latest" }
|
||||
> ```
|
||||
@@ -26,12 +27,13 @@ This installs both `@pinia/nuxt` and `pinia`. If `pinia` isn't installed, add it
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@pinia/nuxt'],
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Auto Imports
|
||||
|
||||
These are automatically available:
|
||||
|
||||
- `usePinia()` - get pinia instance
|
||||
- `defineStore()` - define stores
|
||||
- `storeToRefs()` - extract reactive refs
|
||||
@@ -48,7 +50,7 @@ export default defineNuxtConfig({
|
||||
pinia: {
|
||||
storesDirs: ['./stores/**', './custom-folder/stores/**'],
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Fetching Data in Pages
|
||||
@@ -57,10 +59,10 @@ Use `callOnce()` for SSR-friendly data fetching:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
// Run once, data persists across navigations
|
||||
await callOnce('user', () => store.fetchUser())
|
||||
await callOnce('user', () => store.fetchUser());
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -68,10 +70,10 @@ await callOnce('user', () => store.fetchUser())
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
// Refetch on every navigation (like useFetch)
|
||||
await callOnce('user', () => store.fetchUser(), { mode: 'navigation' })
|
||||
await callOnce('user', () => store.fetchUser(), { mode: 'navigation' });
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -82,13 +84,13 @@ In navigation guards, middlewares, or other stores, pass the `pinia` instance:
|
||||
```ts
|
||||
// middleware/auth.ts
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const store = useStore(nuxtApp.$pinia)
|
||||
const nuxtApp = useNuxtApp();
|
||||
const store = useStore(nuxtApp.$pinia);
|
||||
|
||||
if (to.meta.requiresAuth && !store.isLoggedIn) {
|
||||
return navigateTo('/login')
|
||||
return navigateTo('/login');
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Most of the time, you don't need this - just use stores in components or other injection-aware contexts.
|
||||
@@ -99,18 +101,18 @@ Create a Nuxt plugin:
|
||||
|
||||
```ts
|
||||
// plugins/myPiniaPlugin.ts
|
||||
import { PiniaPluginContext } from 'pinia'
|
||||
import { PiniaPluginContext } from 'pinia';
|
||||
|
||||
function MyPiniaPlugin({ store }: PiniaPluginContext) {
|
||||
store.$subscribe((mutation) => {
|
||||
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`)
|
||||
})
|
||||
return { creationTime: new Date() }
|
||||
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`);
|
||||
});
|
||||
return { creationTime: new Date() };
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(({ $pinia }) => {
|
||||
$pinia.use(MyPiniaPlugin)
|
||||
})
|
||||
$pinia.use(MyPiniaPlugin);
|
||||
});
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
@@ -14,7 +14,7 @@ Pinia works with SSR when stores are called at the top of `setup`, getters, or a
|
||||
```vue
|
||||
<script setup>
|
||||
// ✅ Works - pinia knows the app context in setup
|
||||
const main = useMainStore()
|
||||
const main = useMainStore();
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -23,19 +23,19 @@ const main = useMainStore()
|
||||
Pass the `pinia` instance explicitly:
|
||||
|
||||
```ts
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(pinia);
|
||||
|
||||
router.beforeEach((to) => {
|
||||
// ✅ Pass pinia for correct SSR context
|
||||
const main = useMainStore(pinia)
|
||||
const main = useMainStore(pinia);
|
||||
|
||||
if (to.meta.requiresAuth && !main.isLoggedIn) {
|
||||
return '/login'
|
||||
return '/login';
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## serverPrefetch()
|
||||
@@ -45,10 +45,10 @@ Access pinia via `this.$pinia`:
|
||||
```ts
|
||||
export default {
|
||||
serverPrefetch() {
|
||||
const store = useStore(this.$pinia)
|
||||
return store.fetchData()
|
||||
const store = useStore(this.$pinia);
|
||||
return store.fetchData();
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## onServerPrefetch()
|
||||
@@ -57,11 +57,11 @@ Works normally:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
await store.fetchData()
|
||||
})
|
||||
await store.fetchData();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -74,16 +74,16 @@ Serialize state on server and hydrate on client.
|
||||
Use [devalue](https://github.com/Rich-Harris/devalue) for XSS-safe serialization:
|
||||
|
||||
```ts
|
||||
import devalue from 'devalue'
|
||||
import { createPinia } from 'pinia'
|
||||
import devalue from 'devalue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(pinia);
|
||||
|
||||
// After rendering, state is available
|
||||
const serializedState = devalue(pinia.state.value)
|
||||
const serializedState = devalue(pinia.state.value);
|
||||
// Inject into HTML as global variable
|
||||
```
|
||||
|
||||
@@ -92,13 +92,13 @@ const serializedState = devalue(pinia.state.value)
|
||||
Hydrate before any `useStore()` call:
|
||||
|
||||
```ts
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App)
|
||||
app.use(pinia)
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
app.use(pinia);
|
||||
|
||||
// Hydrate from serialized state (e.g., from window.__pinia)
|
||||
if (typeof window !== 'undefined') {
|
||||
pinia.state.value = JSON.parse(window.__pinia)
|
||||
pinia.state.value = JSON.parse(window.__pinia);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+34
-30
@@ -12,20 +12,20 @@ Stores need the `pinia` instance, which is automatically injected in components.
|
||||
Call stores **after** pinia is installed:
|
||||
|
||||
```ts
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
// ❌ Fails - pinia not created yet
|
||||
const userStore = useUserStore()
|
||||
const userStore = useUserStore();
|
||||
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App)
|
||||
app.use(pinia)
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
app.use(pinia);
|
||||
|
||||
// ✅ Works - pinia is active
|
||||
const userStore = useUserStore()
|
||||
const userStore = useUserStore();
|
||||
```
|
||||
|
||||
## Navigation Guards
|
||||
@@ -33,15 +33,19 @@ const userStore = useUserStore()
|
||||
**Wrong:** Call at module level
|
||||
|
||||
```ts
|
||||
import { createRouter } from 'vue-router'
|
||||
const router = createRouter({ /* ... */ })
|
||||
import { createRouter } from 'vue-router';
|
||||
const router = createRouter({
|
||||
/* ... */
|
||||
});
|
||||
|
||||
// ❌ May fail depending on import order
|
||||
const store = useUserStore()
|
||||
const store = useUserStore();
|
||||
|
||||
router.beforeEach((to) => {
|
||||
if (store.isLoggedIn) { /* ... */ }
|
||||
})
|
||||
if (store.isLoggedIn) {
|
||||
/* ... */
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:** Call inside the guard
|
||||
@@ -49,12 +53,12 @@ router.beforeEach((to) => {
|
||||
```ts
|
||||
router.beforeEach((to) => {
|
||||
// ✅ Called after pinia is installed
|
||||
const store = useUserStore()
|
||||
const store = useUserStore();
|
||||
|
||||
if (to.meta.requiresAuth && !store.isLoggedIn) {
|
||||
return '/login'
|
||||
return '/login';
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## SSR Applications
|
||||
@@ -62,19 +66,19 @@ router.beforeEach((to) => {
|
||||
Always pass the `pinia` instance to `useStore()`:
|
||||
|
||||
```ts
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
const pinia = createPinia();
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(pinia);
|
||||
|
||||
router.beforeEach((to) => {
|
||||
// ✅ Pass pinia instance
|
||||
const main = useMainStore(pinia)
|
||||
const main = useMainStore(pinia);
|
||||
|
||||
if (to.meta.requiresAuth && !main.isLoggedIn) {
|
||||
return '/login'
|
||||
return '/login';
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## serverPrefetch()
|
||||
@@ -84,10 +88,10 @@ Access pinia via `this.$pinia`:
|
||||
```ts
|
||||
export default {
|
||||
serverPrefetch() {
|
||||
const store = useStore(this.$pinia)
|
||||
return store.fetchData()
|
||||
const store = useStore(this.$pinia);
|
||||
return store.fetchData();
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## onServerPrefetch()
|
||||
@@ -96,12 +100,12 @@ Works normally in `<script setup>`:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
|
||||
onServerPrefetch(async () => {
|
||||
// ✅ Just works
|
||||
await store.fetchData()
|
||||
})
|
||||
await store.fetchData();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -10,37 +10,37 @@ description: Unit testing stores and components with @pinia/testing
|
||||
Create a fresh pinia instance for each test:
|
||||
|
||||
```ts
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useCounterStore } from '../src/stores/counter'
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { useCounterStore } from '../src/stores/counter';
|
||||
|
||||
describe('Counter Store', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it('increments', () => {
|
||||
const counter = useCounterStore()
|
||||
expect(counter.n).toBe(0)
|
||||
counter.increment()
|
||||
expect(counter.n).toBe(1)
|
||||
})
|
||||
})
|
||||
const counter = useCounterStore();
|
||||
expect(counter.n).toBe(0);
|
||||
counter.increment();
|
||||
expect(counter.n).toBe(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### With Plugins
|
||||
|
||||
```ts
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import { somePlugin } from '../src/stores/plugin'
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createApp } from 'vue';
|
||||
import { somePlugin } from '../src/stores/plugin';
|
||||
|
||||
const app = createApp({})
|
||||
const app = createApp({});
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createPinia().use(somePlugin)
|
||||
app.use(pinia)
|
||||
setActivePinia(pinia)
|
||||
})
|
||||
const pinia = createPinia().use(somePlugin);
|
||||
app.use(pinia);
|
||||
setActivePinia(pinia);
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Components
|
||||
@@ -54,25 +54,25 @@ npm i -D @pinia/testing
|
||||
Use `createTestingPinia()`:
|
||||
|
||||
```ts
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { useSomeStore } from '@/stores/myStore'
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { useSomeStore } from '@/stores/myStore';
|
||||
|
||||
const wrapper = mount(Counter, {
|
||||
global: {
|
||||
plugins: [createTestingPinia()],
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const store = useSomeStore()
|
||||
const store = useSomeStore();
|
||||
|
||||
// Manipulate state directly
|
||||
store.name = 'new name'
|
||||
store.$patch({ name: 'new name' })
|
||||
store.name = 'new name';
|
||||
store.$patch({ name: 'new name' });
|
||||
|
||||
// Actions are stubbed by default
|
||||
store.someAction()
|
||||
expect(store.someAction).toHaveBeenCalledTimes(1)
|
||||
store.someAction();
|
||||
expect(store.someAction).toHaveBeenCalledTimes(1);
|
||||
```
|
||||
|
||||
## Initial State
|
||||
@@ -90,7 +90,7 @@ const wrapper = mount(Counter, {
|
||||
}),
|
||||
],
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Action Stubbing
|
||||
@@ -98,7 +98,7 @@ const wrapper = mount(Counter, {
|
||||
### Execute Real Actions
|
||||
|
||||
```ts
|
||||
createTestingPinia({ stubActions: false })
|
||||
createTestingPinia({ stubActions: false });
|
||||
```
|
||||
|
||||
### Selective Stubbing
|
||||
@@ -107,24 +107,24 @@ createTestingPinia({ stubActions: false })
|
||||
// Only stub specific actions
|
||||
createTestingPinia({
|
||||
stubActions: ['increment', 'reset'],
|
||||
})
|
||||
});
|
||||
|
||||
// Or use a function
|
||||
createTestingPinia({
|
||||
stubActions: (actionName, store) => {
|
||||
if (actionName.startsWith('set')) return true
|
||||
return false
|
||||
if (actionName.startsWith('set')) return true;
|
||||
return false;
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Mock Action Return Values
|
||||
|
||||
```ts
|
||||
import type { Mock } from 'vitest'
|
||||
import type { Mock } from 'vitest';
|
||||
|
||||
// After getting store
|
||||
store.someAction.mockResolvedValue('mocked value')
|
||||
store.someAction.mockResolvedValue('mocked value');
|
||||
```
|
||||
|
||||
## Mocking Getters
|
||||
@@ -132,14 +132,14 @@ store.someAction.mockResolvedValue('mocked value')
|
||||
Getters are writable in tests:
|
||||
|
||||
```ts
|
||||
const pinia = createTestingPinia()
|
||||
const counter = useCounterStore(pinia)
|
||||
const pinia = createTestingPinia();
|
||||
const counter = useCounterStore(pinia);
|
||||
|
||||
counter.double = 3 // Override computed value
|
||||
counter.double = 3; // Override computed value
|
||||
|
||||
// Reset to default behavior
|
||||
counter.double = undefined
|
||||
counter.double // Now computed normally
|
||||
counter.double = undefined;
|
||||
counter.double; // Now computed normally
|
||||
```
|
||||
|
||||
## Custom Spy Function
|
||||
@@ -147,21 +147,21 @@ counter.double // Now computed normally
|
||||
If not using Jest/Vitest with globals:
|
||||
|
||||
```ts
|
||||
import { vi } from 'vitest'
|
||||
import { vi } from 'vitest';
|
||||
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn,
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
With Sinon:
|
||||
|
||||
```ts
|
||||
import sinon from 'sinon'
|
||||
import sinon from 'sinon';
|
||||
|
||||
createTestingPinia({
|
||||
createSpy: sinon.spy,
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Pinia Plugins in Tests
|
||||
@@ -169,12 +169,12 @@ createTestingPinia({
|
||||
Pass plugins to `createTestingPinia()`:
|
||||
|
||||
```ts
|
||||
import { somePlugin } from '../src/stores/plugin'
|
||||
import { somePlugin } from '../src/stores/plugin';
|
||||
|
||||
createTestingPinia({
|
||||
stubActions: false,
|
||||
plugins: [somePlugin],
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Don't use** `testingPinia.use(MyPlugin)` - pass plugins in options.
|
||||
@@ -182,24 +182,29 @@ createTestingPinia({
|
||||
## Type-Safe Mocked Store
|
||||
|
||||
```ts
|
||||
import type { Mock } from 'vitest'
|
||||
import type { Store, StoreDefinition } from 'pinia'
|
||||
import type { Mock } from 'vitest';
|
||||
import type { Store, StoreDefinition } from 'pinia';
|
||||
|
||||
function mockedStore<TStoreDef extends () => unknown>(
|
||||
useStore: TStoreDef
|
||||
useStore: TStoreDef,
|
||||
): TStoreDef extends StoreDefinition<infer Id, infer State, infer Getters, infer Actions>
|
||||
? Store<Id, State, Record<string, never>, {
|
||||
[K in keyof Actions]: Actions[K] extends (...args: any[]) => any
|
||||
? Mock<Actions[K]>
|
||||
: Actions[K]
|
||||
}>
|
||||
? Store<
|
||||
Id,
|
||||
State,
|
||||
Record<string, never>,
|
||||
{
|
||||
[K in keyof Actions]: Actions[K] extends (...args: any[]) => any
|
||||
? Mock<Actions[K]>
|
||||
: Actions[K];
|
||||
}
|
||||
>
|
||||
: ReturnType<TStoreDef> {
|
||||
return useStore() as any
|
||||
return useStore() as any;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const store = mockedStore(useSomeStore)
|
||||
store.someAction.mockResolvedValue('value') // Typed!
|
||||
const store = mockedStore(useSomeStore);
|
||||
store.someAction.mockResolvedValue('value'); // Typed!
|
||||
```
|
||||
|
||||
## E2E Tests
|
||||
|
||||
@@ -14,7 +14,7 @@ Stores are defined using `defineStore()` with a unique name. Each store has thre
|
||||
Similar to Vue's Options API:
|
||||
|
||||
```ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useCounterStore = defineStore('counter', {
|
||||
state: () => ({
|
||||
@@ -26,10 +26,10 @@ export const useCounterStore = defineStore('counter', {
|
||||
},
|
||||
actions: {
|
||||
increment() {
|
||||
this.count++
|
||||
this.count++;
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Think of `state` as `data`, `getters` as `computed`, and `actions` as `methods`.
|
||||
@@ -39,20 +39,20 @@ Think of `state` as `data`, `getters` as `computed`, and `actions` as `methods`.
|
||||
Uses Composition API syntax - more flexible and powerful:
|
||||
|
||||
```ts
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const name = ref('Eduardo')
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
const count = ref(0);
|
||||
const name = ref('Eduardo');
|
||||
const doubleCount = computed(() => count.value * 2);
|
||||
|
||||
function increment() {
|
||||
count.value++
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, name, doubleCount, increment }
|
||||
})
|
||||
return { count, name, doubleCount, increment };
|
||||
});
|
||||
```
|
||||
|
||||
In Setup Stores: `ref()` → state, `computed()` → getters, `function()` → actions.
|
||||
@@ -63,9 +63,9 @@ In Setup Stores: `ref()` → state, `computed()` → getters, `function()` → a
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
import { useCounterStore } from '@/stores/counter';
|
||||
|
||||
const store = useCounterStore()
|
||||
const store = useCounterStore();
|
||||
// Access: store.count, store.doubleCount, store.increment()
|
||||
</script>
|
||||
```
|
||||
@@ -74,19 +74,19 @@ const store = useCounterStore()
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useCounterStore } from '@/stores/counter';
|
||||
|
||||
const store = useCounterStore()
|
||||
const store = useCounterStore();
|
||||
|
||||
// ❌ Breaks reactivity
|
||||
const { name, doubleCount } = store
|
||||
const { name, doubleCount } = store;
|
||||
|
||||
// ✅ Preserves reactivity for state/getters
|
||||
const { name, doubleCount } = storeToRefs(store)
|
||||
const { name, doubleCount } = storeToRefs(store);
|
||||
|
||||
// ✅ Actions can be destructured directly
|
||||
const { increment } = store
|
||||
const { increment } = store;
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -102,8 +102,8 @@ Type inference works automatically. For complex types:
|
||||
|
||||
```ts
|
||||
interface UserInfo {
|
||||
name: string
|
||||
age: number
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
@@ -111,15 +111,15 @@ export const useUserStore = defineStore('user', {
|
||||
userList: [] as UserInfo[],
|
||||
user: null as UserInfo | null,
|
||||
}),
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Or use an interface for the return type:
|
||||
|
||||
```ts
|
||||
interface State {
|
||||
userList: UserInfo[]
|
||||
user: UserInfo | null
|
||||
userList: UserInfo[];
|
||||
user: UserInfo | null;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
@@ -127,14 +127,14 @@ export const useUserStore = defineStore('user', {
|
||||
userList: [],
|
||||
user: null,
|
||||
}),
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Accessing and Modifying
|
||||
|
||||
```ts
|
||||
const store = useStore()
|
||||
store.count++
|
||||
const store = useStore();
|
||||
store.count++;
|
||||
```
|
||||
|
||||
```vue
|
||||
@@ -150,13 +150,13 @@ Apply multiple changes at once:
|
||||
store.$patch({
|
||||
count: store.count + 1,
|
||||
name: 'DIO',
|
||||
})
|
||||
});
|
||||
|
||||
// Function syntax (for complex mutations)
|
||||
store.$patch((state) => {
|
||||
state.items.push({ name: 'shoes', quantity: 1 })
|
||||
state.hasChanged = true
|
||||
})
|
||||
state.items.push({ name: 'shoes', quantity: 1 });
|
||||
state.hasChanged = true;
|
||||
});
|
||||
```
|
||||
|
||||
### Resetting State
|
||||
@@ -165,30 +165,30 @@ Option Stores have built-in `$reset()`. For Setup Stores, implement your own:
|
||||
|
||||
```ts
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const count = ref(0);
|
||||
|
||||
function $reset() {
|
||||
count.value = 0
|
||||
count.value = 0;
|
||||
}
|
||||
|
||||
return { count, $reset }
|
||||
})
|
||||
return { count, $reset };
|
||||
});
|
||||
```
|
||||
|
||||
### Subscribing to State Changes
|
||||
|
||||
```ts
|
||||
cartStore.$subscribe((mutation, state) => {
|
||||
mutation.type // 'direct' | 'patch object' | 'patch function'
|
||||
mutation.storeId // 'cart'
|
||||
mutation.payload // patch object (only for 'patch object')
|
||||
mutation.type; // 'direct' | 'patch object' | 'patch function'
|
||||
mutation.storeId; // 'cart'
|
||||
mutation.payload; // patch object (only for 'patch object')
|
||||
|
||||
localStorage.setItem('cart', JSON.stringify(state))
|
||||
})
|
||||
localStorage.setItem('cart', JSON.stringify(state));
|
||||
});
|
||||
|
||||
// Options
|
||||
cartStore.$subscribe(callback, { flush: 'sync' }) // Immediate
|
||||
cartStore.$subscribe(callback, { detached: true }) // Keep after unmount
|
||||
cartStore.$subscribe(callback, { flush: 'sync' }); // Immediate
|
||||
cartStore.$subscribe(callback, { detached: true }); // Keep after unmount
|
||||
```
|
||||
|
||||
---
|
||||
@@ -317,28 +317,26 @@ async orderCart() {
|
||||
### Subscribing to Actions
|
||||
|
||||
```ts
|
||||
const unsubscribe = someStore.$onAction(
|
||||
({ name, store, args, after, onError }) => {
|
||||
const startTime = Date.now()
|
||||
console.log(`Start "${name}" with params [${args.join(', ')}]`)
|
||||
const unsubscribe = someStore.$onAction(({ name, store, args, after, onError }) => {
|
||||
const startTime = Date.now();
|
||||
console.log(`Start "${name}" with params [${args.join(', ')}]`);
|
||||
|
||||
after((result) => {
|
||||
console.log(`Finished "${name}" after ${Date.now() - startTime}ms`)
|
||||
})
|
||||
after((result) => {
|
||||
console.log(`Finished "${name}" after ${Date.now() - startTime}ms`);
|
||||
});
|
||||
|
||||
onError((error) => {
|
||||
console.warn(`Failed "${name}": ${error}`)
|
||||
})
|
||||
}
|
||||
)
|
||||
onError((error) => {
|
||||
console.warn(`Failed "${name}": ${error}`);
|
||||
});
|
||||
});
|
||||
|
||||
unsubscribe() // Cleanup
|
||||
unsubscribe(); // Cleanup
|
||||
```
|
||||
|
||||
Keep subscription after component unmount:
|
||||
|
||||
```ts
|
||||
someStore.$onAction(callback, true)
|
||||
someStore.$onAction(callback, true);
|
||||
```
|
||||
|
||||
---
|
||||
@@ -346,8 +344,8 @@ someStore.$onAction(callback, true)
|
||||
## Options API Helpers
|
||||
|
||||
```ts
|
||||
import { mapState, mapWritableState, mapActions } from 'pinia'
|
||||
import { useCounterStore } from '../stores/counter'
|
||||
import { mapState, mapWritableState, mapActions } from 'pinia';
|
||||
import { useCounterStore } from '../stores/counter';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
@@ -359,7 +357,7 @@ export default {
|
||||
methods: {
|
||||
...mapActions(useCounterStore, ['increment']),
|
||||
},
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
@@ -367,17 +365,19 @@ export default {
|
||||
## Accessing Global Providers in Setup Stores
|
||||
|
||||
```ts
|
||||
import { inject } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { defineStore } from 'pinia'
|
||||
import { inject } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useSearchFilters = defineStore('search-filters', () => {
|
||||
const route = useRoute()
|
||||
const appProvided = inject('appProvided')
|
||||
const route = useRoute();
|
||||
const appProvided = inject('appProvided');
|
||||
|
||||
// Don't return these - access them directly in components
|
||||
return { /* ... */ }
|
||||
})
|
||||
return {
|
||||
/* ... */
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
@@ -12,21 +12,23 @@ Pinia stores can leverage Vue composables for reusable stateful logic.
|
||||
Call composables inside the `state` property, but only those returning writable refs:
|
||||
|
||||
```ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
user: useLocalStorage('pinia/auth/login', 'bob'),
|
||||
}),
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Works:** Composables returning `ref()`:
|
||||
|
||||
- `useLocalStorage`
|
||||
- `useAsyncState`
|
||||
|
||||
**Doesn't work in Option Stores:**
|
||||
|
||||
- Composables exposing functions
|
||||
- Composables exposing readonly data
|
||||
|
||||
@@ -35,19 +37,20 @@ export const useAuthStore = defineStore('auth', {
|
||||
More flexible - can use almost any composable:
|
||||
|
||||
```ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { useMediaControls } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia';
|
||||
import { useMediaControls } from '@vueuse/core';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useVideoPlayer = defineStore('video', () => {
|
||||
const videoElement = ref<HTMLVideoElement>()
|
||||
const src = ref('/data/video.mp4')
|
||||
const { playing, volume, currentTime, togglePictureInPicture } =
|
||||
useMediaControls(videoElement, { src })
|
||||
const videoElement = ref<HTMLVideoElement>();
|
||||
const src = ref('/data/video.mp4');
|
||||
const { playing, volume, currentTime, togglePictureInPicture } = useMediaControls(videoElement, {
|
||||
src,
|
||||
});
|
||||
|
||||
function loadVideo(element: HTMLVideoElement, newSrc: string) {
|
||||
videoElement.value = element
|
||||
src.value = newSrc
|
||||
videoElement.value = element;
|
||||
src.value = newSrc;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -57,8 +60,8 @@ export const useVideoPlayer = defineStore('video', () => {
|
||||
currentTime,
|
||||
loadVideo,
|
||||
togglePictureInPicture,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
**Note:** Don't return non-serializable DOM refs like `videoElement` - they're internal implementation details.
|
||||
@@ -70,8 +73,8 @@ export const useVideoPlayer = defineStore('video', () => {
|
||||
Define a `hydrate()` function to handle client-side hydration:
|
||||
|
||||
```ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
@@ -80,9 +83,9 @@ export const useAuthStore = defineStore('auth', {
|
||||
|
||||
hydrate(state, initialState) {
|
||||
// Ignore server state, read from browser
|
||||
state.user = useLocalStorage('pinia/auth/login', 'bob')
|
||||
state.user = useLocalStorage('pinia/auth/login', 'bob');
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Setup Stores with skipHydrate()
|
||||
@@ -90,20 +93,20 @@ export const useAuthStore = defineStore('auth', {
|
||||
Mark state that shouldn't hydrate from server:
|
||||
|
||||
```ts
|
||||
import { defineStore, skipHydrate } from 'pinia'
|
||||
import { useEyeDropper, useLocalStorage } from '@vueuse/core'
|
||||
import { defineStore, skipHydrate } from 'pinia';
|
||||
import { useEyeDropper, useLocalStorage } from '@vueuse/core';
|
||||
|
||||
export const useColorStore = defineStore('colors', () => {
|
||||
const { isSupported, open, sRGBHex } = useEyeDropper()
|
||||
const lastColor = useLocalStorage('lastColor', sRGBHex)
|
||||
const { isSupported, open, sRGBHex } = useEyeDropper();
|
||||
const lastColor = useLocalStorage('lastColor', sRGBHex);
|
||||
|
||||
return {
|
||||
// Skip hydration for client-only state
|
||||
lastColor: skipHydrate(lastColor),
|
||||
open, // Function - no hydration needed
|
||||
open, // Function - no hydration needed
|
||||
isSupported, // Boolean - not reactive
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
`skipHydrate()` only applies to state properties (refs), not functions or non-reactive values.
|
||||
|
||||
@@ -14,53 +14,53 @@ Two stores cannot directly read each other's state during setup:
|
||||
```ts
|
||||
// ❌ Infinite loop
|
||||
const useX = defineStore('x', () => {
|
||||
const y = useY()
|
||||
y.name // Don't read here!
|
||||
return { name: ref('X') }
|
||||
})
|
||||
const y = useY();
|
||||
y.name; // Don't read here!
|
||||
return { name: ref('X') };
|
||||
});
|
||||
|
||||
const useY = defineStore('y', () => {
|
||||
const x = useX()
|
||||
x.name // Don't read here!
|
||||
return { name: ref('Y') }
|
||||
})
|
||||
const x = useX();
|
||||
x.name; // Don't read here!
|
||||
return { name: ref('Y') };
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:** Read in getters, computed, or actions:
|
||||
|
||||
```ts
|
||||
const useX = defineStore('x', () => {
|
||||
const y = useY()
|
||||
const y = useY();
|
||||
|
||||
// ✅ Read in computed/actions
|
||||
function doSomething() {
|
||||
const yName = y.name
|
||||
const yName = y.name;
|
||||
}
|
||||
|
||||
return { name: ref('X'), doSomething }
|
||||
})
|
||||
return { name: ref('X'), doSomething };
|
||||
});
|
||||
```
|
||||
|
||||
## Setup Stores: Use Store at Top
|
||||
|
||||
```ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { useUserStore } from './user'
|
||||
import { defineStore } from 'pinia';
|
||||
import { useUserStore } from './user';
|
||||
|
||||
export const useCartStore = defineStore('cart', () => {
|
||||
const user = useUserStore()
|
||||
const list = ref([])
|
||||
const user = useUserStore();
|
||||
const list = ref([]);
|
||||
|
||||
const summary = computed(() => {
|
||||
return `Hi ${user.name}, you have ${list.value.length} items`
|
||||
})
|
||||
return `Hi ${user.name}, you have ${list.value.length} items`;
|
||||
});
|
||||
|
||||
function purchase() {
|
||||
return apiPurchase(user.id, list.value)
|
||||
return apiPurchase(user.id, list.value);
|
||||
}
|
||||
|
||||
return { list, summary, purchase }
|
||||
})
|
||||
return { list, summary, purchase };
|
||||
});
|
||||
```
|
||||
|
||||
## Shared Getters
|
||||
@@ -68,16 +68,16 @@ export const useCartStore = defineStore('cart', () => {
|
||||
Call `useStore()` inside a getter:
|
||||
|
||||
```ts
|
||||
import { useUserStore } from './user'
|
||||
import { useUserStore } from './user';
|
||||
|
||||
export const useCartStore = defineStore('cart', {
|
||||
getters: {
|
||||
summary(state) {
|
||||
const user = useUserStore()
|
||||
return `Hi ${user.name}, you have ${state.list.length} items`
|
||||
const user = useUserStore();
|
||||
return `Hi ${user.name}, you have ${state.list.length} items`;
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Shared Actions
|
||||
@@ -85,23 +85,23 @@ export const useCartStore = defineStore('cart', {
|
||||
Call `useStore()` inside an action:
|
||||
|
||||
```ts
|
||||
import { useUserStore } from './user'
|
||||
import { apiOrderCart } from './api'
|
||||
import { useUserStore } from './user';
|
||||
import { apiOrderCart } from './api';
|
||||
|
||||
export const useCartStore = defineStore('cart', {
|
||||
actions: {
|
||||
async orderCart() {
|
||||
const user = useUserStore()
|
||||
const user = useUserStore();
|
||||
|
||||
try {
|
||||
await apiOrderCart(user.token, this.items)
|
||||
this.emptyCart()
|
||||
await apiOrderCart(user.token, this.items);
|
||||
this.emptyCart();
|
||||
} catch (err) {
|
||||
displayError(err)
|
||||
displayError(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## SSR: Call Stores Before Await
|
||||
|
||||
@@ -10,18 +10,18 @@ Plugins extend all stores with custom properties, methods, or behavior.
|
||||
## Basic Plugin
|
||||
|
||||
```ts
|
||||
import { createPinia } from 'pinia'
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
function SecretPiniaPlugin() {
|
||||
return { secret: 'the cake is a lie' }
|
||||
return { secret: 'the cake is a lie' };
|
||||
}
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(SecretPiniaPlugin)
|
||||
const pinia = createPinia();
|
||||
pinia.use(SecretPiniaPlugin);
|
||||
|
||||
// In any store
|
||||
const store = useStore()
|
||||
store.secret // 'the cake is a lie'
|
||||
const store = useStore();
|
||||
store.secret; // 'the cake is a lie'
|
||||
```
|
||||
|
||||
## Plugin Context
|
||||
@@ -29,13 +29,13 @@ store.secret // 'the cake is a lie'
|
||||
Plugins receive a context object:
|
||||
|
||||
```ts
|
||||
import { PiniaPluginContext } from 'pinia'
|
||||
import { PiniaPluginContext } from 'pinia';
|
||||
|
||||
export function myPiniaPlugin(context: PiniaPluginContext) {
|
||||
context.pinia // pinia instance
|
||||
context.app // Vue app instance
|
||||
context.store // store being augmented
|
||||
context.options // store definition options
|
||||
context.pinia; // pinia instance
|
||||
context.app; // Vue app instance
|
||||
context.store; // store being augmented
|
||||
context.options; // store definition options
|
||||
}
|
||||
```
|
||||
|
||||
@@ -44,19 +44,19 @@ export function myPiniaPlugin(context: PiniaPluginContext) {
|
||||
Return an object to add properties (tracked in devtools):
|
||||
|
||||
```ts
|
||||
pinia.use(() => ({ hello: 'world' }))
|
||||
pinia.use(() => ({ hello: 'world' }));
|
||||
```
|
||||
|
||||
Or set directly on store:
|
||||
|
||||
```ts
|
||||
pinia.use(({ store }) => {
|
||||
store.hello = 'world'
|
||||
store.hello = 'world';
|
||||
// For devtools visibility in dev mode
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
store._customProperties.add('hello')
|
||||
store._customProperties.add('hello');
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## Adding State
|
||||
@@ -64,15 +64,15 @@ pinia.use(({ store }) => {
|
||||
Add to both `store` and `store.$state` for SSR/devtools:
|
||||
|
||||
```ts
|
||||
import { toRef, ref } from 'vue'
|
||||
import { toRef, ref } from 'vue';
|
||||
|
||||
pinia.use(({ store }) => {
|
||||
if (!store.$state.hasOwnProperty('hasError')) {
|
||||
const hasError = ref(false)
|
||||
store.$state.hasError = hasError
|
||||
const hasError = ref(false);
|
||||
store.$state.hasError = hasError;
|
||||
}
|
||||
store.hasError = toRef(store.$state, 'hasError')
|
||||
})
|
||||
store.hasError = toRef(store.$state, 'hasError');
|
||||
});
|
||||
```
|
||||
|
||||
## Adding External Properties
|
||||
@@ -80,12 +80,12 @@ pinia.use(({ store }) => {
|
||||
Wrap non-reactive objects with `markRaw()`:
|
||||
|
||||
```ts
|
||||
import { markRaw } from 'vue'
|
||||
import { router } from './router'
|
||||
import { markRaw } from 'vue';
|
||||
import { router } from './router';
|
||||
|
||||
pinia.use(({ store }) => {
|
||||
store.router = markRaw(router)
|
||||
})
|
||||
store.router = markRaw(router);
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Store Options
|
||||
@@ -96,24 +96,26 @@ Define custom options consumed by plugins:
|
||||
// Store definition
|
||||
defineStore('search', {
|
||||
actions: {
|
||||
searchContacts() { /* ... */ },
|
||||
searchContacts() {
|
||||
/* ... */
|
||||
},
|
||||
},
|
||||
debounce: {
|
||||
searchContacts: 300,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// Plugin reads custom option
|
||||
import debounce from 'lodash/debounce'
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
pinia.use(({ options, store }) => {
|
||||
if (options.debounce) {
|
||||
return Object.keys(options.debounce).reduce((acc, action) => {
|
||||
acc[action] = debounce(store[action], options.debounce[action])
|
||||
return acc
|
||||
}, {})
|
||||
acc[action] = debounce(store[action], options.debounce[action]);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
For Setup Stores, pass options as third argument:
|
||||
@@ -121,11 +123,13 @@ For Setup Stores, pass options as third argument:
|
||||
```ts
|
||||
defineStore(
|
||||
'search',
|
||||
() => { /* ... */ },
|
||||
() => {
|
||||
/* ... */
|
||||
},
|
||||
{
|
||||
debounce: { searchContacts: 300 },
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## TypeScript Augmentation
|
||||
@@ -133,13 +137,13 @@ defineStore(
|
||||
### Custom Properties
|
||||
|
||||
```ts
|
||||
import 'pinia'
|
||||
import type { Router } from 'vue-router'
|
||||
import 'pinia';
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
declare module 'pinia' {
|
||||
export interface PiniaCustomProperties {
|
||||
router: Router
|
||||
hello: string
|
||||
router: Router;
|
||||
hello: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -149,7 +153,7 @@ declare module 'pinia' {
|
||||
```ts
|
||||
declare module 'pinia' {
|
||||
export interface PiniaCustomStateProperties<S> {
|
||||
hasError: boolean
|
||||
hasError: boolean;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -159,7 +163,7 @@ declare module 'pinia' {
|
||||
```ts
|
||||
declare module 'pinia' {
|
||||
export interface DefineStoreOptionsBase<S, Store> {
|
||||
debounce?: Partial<Record<keyof StoreActions<Store>, number>>
|
||||
debounce?: Partial<Record<keyof StoreActions<Store>, number>>;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -170,11 +174,11 @@ declare module 'pinia' {
|
||||
pinia.use(({ store }) => {
|
||||
store.$subscribe(() => {
|
||||
// React to state changes
|
||||
})
|
||||
});
|
||||
store.$onAction(() => {
|
||||
// React to actions
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Nuxt Plugin
|
||||
@@ -183,18 +187,18 @@ Create a Nuxt plugin to add Pinia plugins:
|
||||
|
||||
```ts
|
||||
// plugins/myPiniaPlugin.ts
|
||||
import { PiniaPluginContext } from 'pinia'
|
||||
import { PiniaPluginContext } from 'pinia';
|
||||
|
||||
function MyPiniaPlugin({ store }: PiniaPluginContext) {
|
||||
store.$subscribe((mutation) => {
|
||||
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`)
|
||||
})
|
||||
return { creationTime: new Date() }
|
||||
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`);
|
||||
});
|
||||
return { creationTime: new Date() };
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(({ $pinia }) => {
|
||||
$pinia.use(MyPiniaPlugin)
|
||||
})
|
||||
$pinia.use(MyPiniaPlugin);
|
||||
});
|
||||
```
|
||||
|
||||
<!--
|
||||
|
||||
Reference in New Issue
Block a user