From: Eduardo San Martin Morote Date: Tue, 10 Jan 2023 17:33:05 +0000 (+0100) Subject: docs: complete migration to vitepress 1 X-Git-Tag: @pinia/nuxt@0.4.7~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=51b7205731d1fdef0e8f9ef83b263e8022e84464;p=thirdparty%2Fvuejs%2Fpinia.git docs: complete migration to vitepress 1 --- diff --git a/packages/docs-new/.gitignore b/packages/docs-new/.gitignore new file mode 100644 index 00000000..4f2fc4ac --- /dev/null +++ b/packages/docs-new/.gitignore @@ -0,0 +1 @@ +.vitepress/cache diff --git a/packages/docs-new/.vitepress/config/en.ts b/packages/docs-new/.vitepress/config/en.ts new file mode 100644 index 00000000..d00e7b46 --- /dev/null +++ b/packages/docs-new/.vitepress/config/en.ts @@ -0,0 +1,142 @@ +import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress' + +export const META_URL = 'https://pinia.vuejs.org' +export const META_TITLE = 'Pinia 🍍' +export const META_DESCRIPTION = + 'Intuitive, type safe, light and flexible Store for Vue' + +export const enConfig: LocaleSpecificConfig = { + description: META_DESCRIPTION, + head: [ + ['meta', { property: 'og:url', content: META_URL }], + ['meta', { property: 'og:description', content: META_DESCRIPTION }], + ['meta', { property: 'twitter:url', content: META_URL }], + ['meta', { property: 'twitter:title', content: META_TITLE }], + ['meta', { property: 'twitter:description', content: META_DESCRIPTION }], + ], + + themeConfig: { + editLink: { + pattern: 'https://github.com/vuejs/pinia/edit/v2/packages/docs/:path', + text: 'Suggest changes to this page', + }, + + nav: [ + // { text: 'Config', link: '/config/' }, + // { text: 'Plugins', link: '/plugins/' }, + { text: 'API', link: '/api/', activeMatch: '^/api/' }, + { text: 'Cookbook', link: '/cookbook/', activeMatch: '^/cookbook/' }, + { + text: 'Links', + items: [ + { + text: 'Discussions', + link: 'https://github.com/vuejs/pinia/discussions', + }, + { + text: 'Changelog', + link: 'https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md', + }, + ], + }, + ], + + sidebar: { + '/api/': [ + { + text: 'packages', + items: [ + { text: 'pinia', link: '/api/modules/pinia.html' }, + { text: '@pinia/nuxt', link: '/api/modules/pinia_nuxt.html' }, + { + text: '@pinia/testing', + link: '/api/modules/pinia_testing.html', + }, + ], + }, + ], + // catch-all fallback + '/': [ + { + text: 'Introduction', + items: [ + { + text: 'What is Pinia?', + link: '/introduction.html', + }, + { + text: 'Getting Started', + link: '/getting-started.html', + }, + ], + }, + { + text: 'Core Concepts', + items: [ + { text: 'Defining a Store', link: '/core-concepts/' }, + { text: 'State', link: '/core-concepts/state.html' }, + { text: 'Getters', link: '/core-concepts/getters.html' }, + { text: 'Actions', link: '/core-concepts/actions.html' }, + { text: 'Plugins', link: '/core-concepts/plugins.html' }, + { + text: 'Stores outside of components', + link: '/core-concepts/outside-component-usage.html', + }, + ], + }, + { + text: 'Server-Side Rendering (SSR)', + items: [ + { + text: 'Vue and Vite', + link: '/ssr/', + }, + { + text: 'Nuxt.js', + link: '/ssr/nuxt.html', + }, + ], + }, + { + text: 'Cookbook', + collapsible: true, + collapsed: false, + items: [ + { + text: 'Index', + link: '/cookbook/', + }, + { + text: 'Migration from Vuex ≤4', + link: '/cookbook/migration-vuex.html', + }, + { + text: 'Hot Module Replacement', + link: '/cookbook/hot-module-replacement.html', + }, + { + text: 'Testing', + link: '/cookbook/testing.html', + }, + { + text: 'Usage without setup()', + link: '/cookbook/options-api.html', + }, + { + text: 'Composing Stores', + link: '/cookbook/composing-stores.html', + }, + { + text: 'Migration from v0/v1 to v2', + link: '/cookbook/migration-v1-v2.html', + }, + { + text: 'Dealing with composables', + link: '/cookbook/composables.html', + }, + ], + }, + ], + }, + }, +} diff --git a/packages/docs-new/.vitepress/config/index.ts b/packages/docs-new/.vitepress/config/index.ts new file mode 100644 index 00000000..0feb87ab --- /dev/null +++ b/packages/docs-new/.vitepress/config/index.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitepress' +import { enConfig } from './en' +import { sharedConfig } from './shared' +import { zhConfig } from './zh' + +export default defineConfig({ + ...sharedConfig, + + locales: { + root: { label: 'English', lang: 'en-US', link: '/', ...enConfig }, + zh: { label: '简体中文', lang: 'zh-CN', link: '/zh/', ...zhConfig }, + }, +}) diff --git a/packages/docs-new/.vitepress/config/shared.ts b/packages/docs-new/.vitepress/config/shared.ts new file mode 100644 index 00000000..0a20fd94 --- /dev/null +++ b/packages/docs-new/.vitepress/config/shared.ts @@ -0,0 +1,131 @@ +import { defineConfig, HeadConfig } from 'vitepress' + +export const META_IMAGE = 'https://pinia.vuejs.org/social.png' +export const isProduction = process.env.NODE_ENV === 'production' + +const productionHead: HeadConfig[] = [ + [ + 'script', + { + src: 'https://unpkg.com/thesemetrics@latest', + async: '', + type: 'text/javascript', + }, + ], +] + +export const sharedConfig = defineConfig({ + title: 'Pinia', + appearance: 'dark', + + markdown: { + theme: { + dark: 'dracula-soft', + light: 'vitesse-light', + }, + attrs: { + leftDelimiter: '%{', + rightDelimiter: '}%', + }, + // TODO: are these needed for API maybe? + // anchor: { + // permalink: renderPermaLink, + // }, + // config: (md) => { + // md.use(MarkDownItCustomAnchor) + // }, + }, + + head: [ + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], + ['link', { rel: 'icon', type: 'image/png', href: '/logo.png' }], + + [ + 'meta', + { name: 'wwads-cn-verify', content: '5878a7ab84fb43402106c575658472fa' }, + ], + + [ + 'meta', + { + property: 'og:type', + content: 'website', + }, + ], + + [ + 'meta', + { + property: 'twitter:card', + content: 'summary_large_image', + }, + ], + [ + 'meta', + { + property: 'twitter:image', + content: META_IMAGE, + }, + ], + + [ + 'link', + { + rel: 'preload', + href: '/dank-mono.css', + as: 'style', + onload: "this.onload=null;this.rel='stylesheet'", + }, + ], + + [ + 'script', + { + src: 'https://vueschool.io/banners/main.js', + // @ts-expect-error: vitepress bug + async: true, + type: 'text/javascript', + }, + ], + + ...(isProduction ? productionHead : []), + ], + + themeConfig: { + logo: '/logo.svg', + + socialLinks: [ + { icon: 'twitter', link: 'https://twitter.com/posva' }, + { + icon: 'github', + link: 'https://github.com/vuejs/pinia', + }, + { + icon: 'discord', + link: 'https://chat.vuejs.org', + }, + ], + + footer: { + copyright: 'Copyright © 2019-present Eduardo San Martin Morote', + message: 'Released under the MIT License.', + }, + + editLink: { + pattern: 'https://github.com/vuejs/pinia/edit/v2/packages/docs/:path', + text: 'Suggest changes', + }, + + algolia: { + appId: '69Y3N7LHI2', + apiKey: '45441f4b65a2f80329fd45c7cb371fea', + indexName: 'pinia', + }, + + carbonAds: { + code: 'CEBICK3I', + // custom: 'CEBICK3M', + placement: 'routervuejsorg', + }, + }, +}) diff --git a/packages/docs-new/.vitepress/config/zh.ts b/packages/docs-new/.vitepress/config/zh.ts new file mode 100644 index 00000000..88837575 --- /dev/null +++ b/packages/docs-new/.vitepress/config/zh.ts @@ -0,0 +1,142 @@ +import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress' + +export const META_URL = 'https://pinia.vuejs.org' +export const META_TITLE = 'Pinia 🍍' +export const META_DESCRIPTION = '值得你喜欢的 Vue Store' +// TODO: translation of this +// 'Intuitive, type safe, light and flexible Store for Vue' + +export const zhConfig: LocaleSpecificConfig = { + description: META_DESCRIPTION, + head: [ + ['meta', { property: 'og:url', content: META_URL }], + ['meta', { property: 'og:description', content: META_DESCRIPTION }], + ['meta', { property: 'twitter:url', content: META_URL }], + ['meta', { property: 'twitter:title', content: META_TITLE }], + ['meta', { property: 'twitter:description', content: META_DESCRIPTION }], + ], + + themeConfig: { + editLink: { + // TODO: do we need the /zh/ + pattern: 'https://github.com/vuejs/pinia/edit/v2/packages/docs/:path', + text: '对本页提出修改建议', + }, + + nav: [ + // { text: 'Config', link: '/config/' }, + // { text: 'Plugins', link: '/plugins/' }, + { text: 'API', link: '/zh/api/', activeMatch: '^/zh/api/' }, + { text: '手册', link: '/zh/cookbook/', activeMatch: '^/zh/cookbook/' }, + { + text: '相关链接', + items: [ + { + text: '论坛', + link: 'https://github.com/vuejs/pinia/discussions', + }, + { + text: '更新日志', + link: 'https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md', + }, + ], + }, + ], + sidebar: { + '/zh/api/': [ + { + text: 'packages', + items: [ + { text: 'pinia', link: '/zh/api/modules/pinia.html' }, + { text: '@pinia/nuxt', link: '/zh/api/modules/pinia_nuxt.html' }, + { + text: '@pinia/testing', + link: '/zh/api/modules/pinia_testing.html', + }, + ], + }, + ], + '/zh/': [ + { + text: '介绍', + items: [ + { + text: 'Pinia 是什么?', + link: '/zh/introduction.html', + }, + { + text: '开始', + link: '/zh/getting-started.html', + }, + ], + }, + { + text: '核心概念', + items: [ + { text: '定义 Store', link: '/zh/core-concepts/' }, + { text: 'State', link: '/zh/core-concepts/state.html' }, + { text: 'Getter', link: '/zh/core-concepts/getters.html' }, + { text: 'Action', link: '/zh/core-concepts/actions.html' }, + { text: '插件', link: '/zh/core-concepts/plugins.html' }, + { + text: '组件外的 Store', + link: '/zh/core-concepts/outside-component-usage.html', + }, + ], + }, + { + text: '服务端渲染 (SSR)', + items: [ + { + text: 'Vue 与 Vite', + link: '/zh/ssr/', + }, + { + text: 'Nuxt.js', + link: '/zh/ssr/nuxt.html', + }, + ], + }, + { + text: '手册', + collapsible: true, + collapsed: false, + items: [ + { + text: '目录', + link: '/zh/cookbook/', + }, + { + text: '从 Vuex ≤4 迁移', + link: '/zh/cookbook/migration-vuex.html', + }, + { + text: '热更新', + link: '/zh/cookbook/hot-module-replacement.html', + }, + { + text: '测试', + link: '/zh/cookbook/testing.html', + }, + { + text: '不使用 setup() 的用法', + link: '/zh/cookbook/options-api.html', + }, + { + text: '组合式 Stores', + link: '/zh/cookbook/composing-stores.html', + }, + { + text: '从 v0/v1 迁移至 v2', + link: '/zh/cookbook/migration-v1-v2.html', + }, + { + text: '处理组合式函数', + link: '/zh/cookbook/composables.html', + }, + ], + }, + ], + }, + }, +} diff --git a/packages/docs-new/.vitepress/theme/components/HomeSponsors.vue b/packages/docs-new/.vitepress/theme/components/HomeSponsors.vue new file mode 100644 index 00000000..458b02c7 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/HomeSponsors.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/packages/docs-new/.vitepress/theme/components/HomeSponsorsGroup.vue b/packages/docs-new/.vitepress/theme/components/HomeSponsorsGroup.vue new file mode 100644 index 00000000..8e876c6f --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/HomeSponsorsGroup.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/packages/docs-new/.vitepress/theme/components/PiniaLogo.vue b/packages/docs-new/.vitepress/theme/components/PiniaLogo.vue new file mode 100644 index 00000000..99c200ff --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/PiniaLogo.vue @@ -0,0 +1,324 @@ + + + + + + + diff --git a/packages/docs-new/.vitepress/theme/components/VueMasteryHomeLink.vue b/packages/docs-new/.vitepress/theme/components/VueMasteryHomeLink.vue new file mode 100644 index 00000000..6bde6b7c --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/VueMasteryHomeLink.vue @@ -0,0 +1,63 @@ + + + + + + \ No newline at end of file diff --git a/packages/docs-new/.vitepress/theme/components/VueMasteryLogoLink.vue b/packages/docs-new/.vitepress/theme/components/VueMasteryLogoLink.vue new file mode 100644 index 00000000..0589d3f8 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/VueMasteryLogoLink.vue @@ -0,0 +1,77 @@ + + + + + \ No newline at end of file diff --git a/packages/docs-new/.vitepress/theme/components/VueSchoolLink.vue b/packages/docs-new/.vitepress/theme/components/VueSchoolLink.vue new file mode 100644 index 00000000..42592481 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/VueSchoolLink.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/packages/docs-new/.vitepress/theme/components/sponsors.json b/packages/docs-new/.vitepress/theme/components/sponsors.json new file mode 100644 index 00000000..c0843da0 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/components/sponsors.json @@ -0,0 +1,51 @@ +{ + "platinum": [], + "gold": [ + { + "href": "https://vuejobs.com/?utm_source=vuerouter&utm_campaign=sponsor", + "alt": "VueJobs", + "imgSrcLight": "https://posva-sponsors.pages.dev/logos/vuejobs.svg", + "imgSrcDark": "https://posva-sponsors.pages.dev/logos/vuejobs.svg" + } + ], + "silver": [ + { + "href": "https://www.vuemastery.com/", + "alt": "VueMastery", + "imgSrcLight": "https://posva-sponsors.pages.dev/logos/vuemastery-light.svg", + "imgSrcDark": "https://posva-sponsors.pages.dev/logos/vuemastery-dark.png" + }, + { + "href": "https://www.prefect.io/", + "imgSrcLight": "https://posva-sponsors.pages.dev/logos/prefectlogo-light.svg", + "imgSrcDark": "https://posva-sponsors.pages.dev/logos/prefectlogo-dark.svg", + "alt": "Prefect" + } + ], + "bronze": [ + { + "alt": "Stanislas Ormières", + "href": "https://stormier.ninja", + "imgSrcDark": "https://avatars.githubusercontent.com/u/2486424?u=7b0c73ae5d090ce53bf59473094e9606fe082c59&v=4", + "imgSrcLight": "https://avatars.githubusercontent.com/u/2486424?u=7b0c73ae5d090ce53bf59473094e9606fe082c59&v=4" + }, + { + "alt": "Antony Konstantinidis", + "href": "www.vuejs.de", + "imgSrcDark": "https://avatars.githubusercontent.com/u/4183726?u=6b50a8ea16de29d2982f43c5640b1db9299ebcd1&v=4", + "imgSrcLight": "https://avatars.githubusercontent.com/u/4183726?u=6b50a8ea16de29d2982f43c5640b1db9299ebcd1&v=4" + }, + { + "href": "https://storyblok.com", + "imgSrcLight": "https://posva-sponsors.pages.dev/logos/storyblok.png", + "imgSrcDark": "https://posva-sponsors.pages.dev/logos/storyblok.png", + "alt": "Storyblok" + }, + { + "href": "https://nuxtjs.org", + "imgSrcLight": "https://posva-sponsors.pages.dev/logos/nuxt-light.svg", + "imgSrcDark": "https://posva-sponsors.pages.dev/logos/nuxt-dark.svg", + "alt": "NuxtJS" + } + ] +} diff --git a/packages/docs-new/.vitepress/theme/index.ts b/packages/docs-new/.vitepress/theme/index.ts new file mode 100644 index 00000000..089a8019 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/index.ts @@ -0,0 +1,35 @@ +import { h, watchEffect } from 'vue' +import { Theme, useData } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +// import AsideSponsors from './components/AsideSponsors.vue' +// import HomeSponsors from './components/HomeSponsors.vue' +import './styles/vars.css' +import VueSchoolLink from './components/VueSchoolLink.vue' +import VueMasteryLogoLink from './components/VueMasteryLogoLink.vue' + +const theme: Theme = { + ...DefaultTheme, + // Layout() { + // return h(DefaultTheme.Layout, null, { + // 'home-features-after': () => h(HomeSponsors), + // 'aside-ads-before': () => h(AsideSponsors), + // }) + // }, + + enhanceApp({ app }) { + app.component('VueSchoolLink', VueSchoolLink) + app.component('VueMasteryLogoLink', VueMasteryLogoLink) + }, + + // TODO: real date + // setup() { + // const { lang } = useData() + // watchEffect(() => { + // if (typeof document !== 'undefined') { + // document.cookie = `nf_lang=${lang.value}; expires=Sun, 1 Jan 2023 00:00:00 UTC; path=/` + // } + // }) + // }, +} + +export default theme diff --git a/packages/docs-new/.vitepress/theme/styles/vars.css b/packages/docs-new/.vitepress/theme/styles/vars.css new file mode 100644 index 00000000..1db34086 --- /dev/null +++ b/packages/docs-new/.vitepress/theme/styles/vars.css @@ -0,0 +1,211 @@ +/** + * Colors + * -------------------------------------------------------------------------- */ + +:root { + --c-yellow: #ffd859; + --c-yellow-light: #f7d336; + --c-yellow-lighter: #dec96e; + --c-yellow-dark: #ecb732; + --c-yellow-darker: #c99513; + + --c-teal: #086367; + --c-teal-light: #33898d; + + --c-white-dark: #f8f8f8; + --vp-c-black: #111827; + --vp-c-black-light: #161f32; + --vp-c-black-lighter: #262a44; + + --c-green-light: #8ae99c; + --c-green: #52ce63; + --c-green-dark: #51a256; + + --vp-c-text-dark-1: #d9e6eb; + --vp-c-text-dark-2: #c4dde6; + --vp-c-text-dark-3: #abc4cc; + --c-text-light-1: #2c3e50; + --c-text-light-2: #476582; + --c-text-light-3: #90a4b7; + /* #f9fafb */ + + /* light theme is a bit different */ + --vp-c-brand: var(--c-teal); + --vp-c-brand-dark: var(--c-yellow-dark); + --vp-c-brand-light: var(--c-teal-light); + --vp-c-brand-lighter: var(--c-yellow-lighter); + --vp-c-brand-lightest: var(--c-yellow-lighter); + + --vp-c-brand-dark: var(--c-yellow-dark); + --vp-c-brand-darker: var(--c-yellow-darker); + --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); + --vp-c-brand-text: var(--c-text-light-1); + --c-bg-accent: var(--c-white-dark); + --code-bg-color: var(--c-white-dark); + --code-inline-bg-color: var(--c-white-dark); + --code-font-family: 'dm', source-code-pro, Menlo, Monaco, Consolas, + 'Courier New', monospace; + --code-font-size: 16px; + + --vp-code-block-bg: var(--vp-c-bg-soft); + --vp-code-line-highlight-color: rgba(0, 0, 0, 0.075); + + /* --vp-c-brand: #646cff; */ + /* --vp-c-brand-light: #747bff; */ + /* --vp-c-brand-lighter: #9499ff; */ + /* --vp-c-brand-lightest: #bcc0ff; */ + /* --vp-c-brand-dark: #535bf2; */ + /* --vp-c-brand-darker: #454ce1; */ + /* --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); */ +} + +html.dark:root { + /* --c-black: #ffffff; + --c-white: #000000; */ + /* --c-divider-light: rgba(60, 60, 67, 0.12); + --c-divider-dark: rgba(84, 84, 88, 0.48); */ + /* --c-brand-light: var(--c-yellow-light); */ + + --vp-c-brand: var(--c-yellow); + --vp-c-brand-dark: var(--c-yellow-dark); + --vp-c-brand-light: var(--c-yellow-light); + + --vp-c-bg-alpha-with-backdrop: rgba(20, 25, 36, 0.7); + --vp-c-bg-alpha-without-backdrop: rgba(20, 25, 36, 0.9); + + --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5); + + --vp-c-text: var(--c-text-dark-1); + --vp-c-brand-text: var(--c-text-light-1); + --c-text-light: var(--c-text-dark-2); + --c-text-lighter: var(--c-text-dark-3); + --c-divider: var(--c-divider-dark); + --c-bg-accent: var(--vp-c-black-light); + /* --vp-code-inline-bg: var(--vp-c-black-light); */ + + --vp-c-bg: var(--vp-c-black); + --vp-c-bg-soft: var(--vp-c-black-light); + --vp-c-bg-mute: var(--vp-c-black-light); + + --vp-home-hero-name-background: -webkit-linear-gradient( + 78deg, + var(--c-yellow-light) 30%, + var(--c-green-light) + ); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: var(--c-yellow-dark); + --vp-button-brand-text: var(--vp-c-brand-text); + --vp-button-brand-bg: var(--c-yellow); + --vp-button-brand-hover-border: var(--c-yellow-light); + --vp-button-brand-hover-text: var(--vp-c-brand-text); + --vp-button-brand-hover-bg: var(--c-yellow-light); + --vp-button-brand-active-border: var(--c-yellow-dark); + --vp-button-brand-active-text: var(--vp-c-brand-text); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 78deg, + var(--c-green-dark) 30%, + var(--c-green-light) + ); + --vp-home-hero-image-background-image: linear-gradient( + 15deg, + var(--c-yellow-light) 65%, + var(--c-green-light) 30% + ); + --vp-home-hero-image-filter: blur(40px); +} + +.VPHero .VPImage.image-src { + max-height: 192px; +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } + .VPHero .VPImage.image-src { + max-height: 256px; + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } + .VPHero .VPImage.image-src { + max-height: 320px; + } +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand) !important; +} + +html.dark .DocSearch, +html.dark .DocSearch-Modal { + /* --docsearch-text-color: var(--c-white-dark); */ + --docsearch-container-background: rgba(9, 10, 17, 0.8); + --docsearch-modal-background: var(--vp-c-black); + --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309; + /* --docsearch-searchbox-background: var(--c-black-lighter); */ + /* --docsearch-searchbox-focus-background: var(--c-black-light); */ + --docsearch-hit-color: var(--c-text-dark-1); + --docsearch-hit-active-color: var(--c-text-light-1); + --docsearch-hit-shadow: none; + --docsearch-hit-background: var(--vp-c-black-light); + --docsearch-footer-background: var(--vp-c-black-light); + --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), + 0 -4px 8px 0 rgba(0, 0, 0, 0.2); + --docsearch-logo-color: var(--vp-c-brand); + --docsearch-muted-color: var(--c-text-light-3); +} + +html.dark .DocSearch-Logo svg .cls-1, +html.dark .DocSearch-Logo svg .cls-2 { + fill: var(--vp-c-brand); +} + +.become-sponsor { + font-size: 0.9em; + font-weight: 700; + width: auto; + text-align: center; + background-color: transparent; + padding: 0.75em 2em; + border-radius: 2em; + transition: all 0.15s ease; + box-sizing: border-box; + border: 2px solid var(--c-yellow-dark); +} + +.become-sponsor:hover { + background-color: var(--c-yellow); + text-decoration: none; + border-color: var(--c-yellow); + color: var(--c-text-light-1); +} + +.sponsors-top .become-sponsor { + font-size: 0.75em; + padding: 0.2em; + width: auto; + max-width: 150px; +} diff --git a/packages/docs-new/api/enums/pinia.MutationType.md b/packages/docs-new/api/enums/pinia.MutationType.md new file mode 100644 index 00000000..1654ed4d --- /dev/null +++ b/packages/docs-new/api/enums/pinia.MutationType.md @@ -0,0 +1,45 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / MutationType + +# Enumeration: MutationType + +[pinia](../modules/pinia.md).MutationType + +Possible types for SubscriptionCallback + +## Enumeration Members + +### direct + +• **direct** = ``"direct"`` + +Direct mutation of the state: + +- `store.name = 'new name'` +- `store.$state.name = 'new name'` +- `store.list.push('new item')` + +___ + +### patchFunction + +• **patchFunction** = ``"patch function"`` + +Mutated the state with `$patch` and a function + +- `store.$patch(state => state.name = 'newName')` + +___ + +### patchObject + +• **patchObject** = ``"patch object"`` + +Mutated the state with `$patch` and an object + +- `store.$patch({ name: 'newName' })` diff --git a/packages/docs-new/api/index.md b/packages/docs-new/api/index.md new file mode 100644 index 00000000..f25b0e46 --- /dev/null +++ b/packages/docs-new/api/index.md @@ -0,0 +1,9 @@ +API Documentation + +# API Documentation + +## Modules + +- [@pinia/nuxt](modules/pinia_nuxt.md) +- [@pinia/testing](modules/pinia_testing.md) +- [pinia](modules/pinia.md) diff --git a/packages/docs-new/api/interfaces/pinia.DefineSetupStoreOptions.md b/packages/docs-new/api/interfaces/pinia.DefineSetupStoreOptions.md new file mode 100644 index 00000000..7b5cbf10 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.DefineSetupStoreOptions.md @@ -0,0 +1,43 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / DefineSetupStoreOptions + +# Interface: DefineSetupStoreOptions + +[pinia](../modules/pinia.md).DefineSetupStoreOptions + +Options parameter of `defineStore()` for setup stores. Can be extended to +augment stores with the plugin API. + +**`See`** + +[DefineStoreOptionsBase](pinia.DefineStoreOptionsBase.md). + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +## Hierarchy + +- [`DefineStoreOptionsBase`](pinia.DefineStoreOptionsBase.md)<`S`, [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\>\> + + ↳ **`DefineSetupStoreOptions`** + +## Properties + +### actions + +• `Optional` **actions**: `A` + +Extracted actions. Added by useStore(). SHOULD NOT be added by the user when +creating the store. Can be used in plugins to get the list of actions in a +store defined with a setup function. Note this is always defined diff --git a/packages/docs-new/api/interfaces/pinia.DefineStoreOptions.md b/packages/docs-new/api/interfaces/pinia.DefineStoreOptions.md new file mode 100644 index 00000000..5cc1fa41 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.DefineStoreOptions.md @@ -0,0 +1,112 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / DefineStoreOptions + +# Interface: DefineStoreOptions + +[pinia](../modules/pinia.md).DefineStoreOptions + +Options parameter of `defineStore()` for option stores. Can be extended to +augment stores with the plugin API. + +**`See`** + +[DefineStoreOptionsBase](pinia.DefineStoreOptionsBase.md). + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +## Hierarchy + +- [`DefineStoreOptionsBase`](pinia.DefineStoreOptionsBase.md)<`S`, [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\>\> + + ↳ **`DefineStoreOptions`** + +## Properties + +### actions + +• `Optional` **actions**: `A` & `ThisType`<`A` & `UnwrapRef`<`S`\> & [`_StoreWithState`](pinia._StoreWithState.md)<`Id`, `S`, `G`, `A`\> & [`_StoreWithGetters`](../modules/pinia.md#_storewithgetters)<`G`\> & [`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\>\> + +Optional object of actions. + +___ + +### getters + +• `Optional` **getters**: `G` & `ThisType`<`UnwrapRef`<`S`\> & [`_StoreWithGetters`](../modules/pinia.md#_storewithgetters)<`G`\> & [`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\>\> & [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> + +Optional object of getters. + +___ + +### id + +• **id**: `Id` + +Unique string key to identify the store across the application. + +___ + +### state + +• `Optional` **state**: () => `S` + +#### Type declaration + +▸ (): `S` + +Function to create a fresh state. **Must be an arrow function** to ensure +correct typings! + +##### Returns + +`S` + +## Methods + +### hydrate + +▸ `Optional` **hydrate**(`storeState`, `initialState`): `void` + +Allows hydrating the store during SSR when complex state (like client side only refs) are used in the store +definition and copying the value from `pinia.state` isn't enough. + +**`Example`** + +If in your `state`, you use any `customRef`s, any `computed`s, or any `ref`s that have a different value on +Server and Client, you need to manually hydrate them. e.g., a custom ref that is stored in the local +storage: + +```ts +const useStore = defineStore('main', { + state: () => ({ + n: useLocalStorage('key', 0) + }), + hydrate(storeState, initialState) { + // @ts-expect-error: https://github.com/microsoft/TypeScript/issues/43826 + storeState.n = useLocalStorage('key', 0) + } +}) +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `storeState` | `UnwrapRef`<`S`\> | the current state in the store | +| `initialState` | `UnwrapRef`<`S`\> | initialState | + +#### Returns + +`void` diff --git a/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsBase.md b/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsBase.md new file mode 100644 index 00000000..7a319ed1 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsBase.md @@ -0,0 +1,30 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / DefineStoreOptionsBase + +# Interface: DefineStoreOptionsBase + +[pinia](../modules/pinia.md).DefineStoreOptionsBase + +Options passed to `defineStore()` that are common between option and setup +stores. Extend this interface if you want to add custom options to both kinds +of stores. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) | +| `Store` | `Store` | + +## Hierarchy + +- **`DefineStoreOptionsBase`** + + ↳ [`DefineStoreOptions`](pinia.DefineStoreOptions.md) + + ↳ [`DefineSetupStoreOptions`](pinia.DefineSetupStoreOptions.md) diff --git a/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsInPlugin.md b/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsInPlugin.md new file mode 100644 index 00000000..15777ac9 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.DefineStoreOptionsInPlugin.md @@ -0,0 +1,113 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / DefineStoreOptionsInPlugin + +# Interface: DefineStoreOptionsInPlugin + +[pinia](../modules/pinia.md).DefineStoreOptionsInPlugin + +Available `options` when creating a pinia plugin. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +## Hierarchy + +- `Omit`<[`DefineStoreOptions`](pinia.DefineStoreOptions.md)<`Id`, `S`, `G`, `A`\>, ``"id"`` \| ``"actions"``\> + + ↳ **`DefineStoreOptionsInPlugin`** + +## Properties + +### actions + +• **actions**: `A` + +Extracted object of actions. Added by useStore() when the store is built +using the setup API, otherwise uses the one passed to `defineStore()`. +Defaults to an empty object if no actions are defined. + +___ + +### getters + +• `Optional` **getters**: `G` & `ThisType`<`UnwrapRef`<`S`\> & [`_StoreWithGetters`](../modules/pinia.md#_storewithgetters)<`G`\> & [`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\>\> & [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> + +Optional object of getters. + +#### Inherited from + +Omit.getters + +___ + +### state + +• `Optional` **state**: () => `S` + +#### Type declaration + +▸ (): `S` + +Function to create a fresh state. **Must be an arrow function** to ensure +correct typings! + +##### Returns + +`S` + +#### Inherited from + +Omit.state + +## Methods + +### hydrate + +▸ `Optional` **hydrate**(`storeState`, `initialState`): `void` + +Allows hydrating the store during SSR when complex state (like client side only refs) are used in the store +definition and copying the value from `pinia.state` isn't enough. + +**`Example`** + +If in your `state`, you use any `customRef`s, any `computed`s, or any `ref`s that have a different value on +Server and Client, you need to manually hydrate them. e.g., a custom ref that is stored in the local +storage: + +```ts +const useStore = defineStore('main', { + state: () => ({ + n: useLocalStorage('key', 0) + }), + hydrate(storeState, initialState) { + // @ts-expect-error: https://github.com/microsoft/TypeScript/issues/43826 + storeState.n = useLocalStorage('key', 0) + } +}) +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `storeState` | `UnwrapRef`<`S`\> | the current state in the store | +| `initialState` | `UnwrapRef`<`S`\> | initialState | + +#### Returns + +`void` + +#### Inherited from + +Omit.hydrate diff --git a/packages/docs-new/api/interfaces/pinia.MapStoresCustomization.md b/packages/docs-new/api/interfaces/pinia.MapStoresCustomization.md new file mode 100644 index 00000000..7271366a --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.MapStoresCustomization.md @@ -0,0 +1,16 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / MapStoresCustomization + +# Interface: MapStoresCustomization + +[pinia](../modules/pinia.md).MapStoresCustomization + +Interface to allow customizing map helpers. Extend this interface with the +following properties: + +- `suffix`: string. Affects the suffix of `mapStores()`, defaults to `Store`. diff --git a/packages/docs-new/api/interfaces/pinia.Pinia.md b/packages/docs-new/api/interfaces/pinia.Pinia.md new file mode 100644 index 00000000..9c88b0ca --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.Pinia.md @@ -0,0 +1,65 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / Pinia + +# Interface: Pinia + +[pinia](../modules/pinia.md).Pinia + +Every application must own its own pinia to be able to create stores + +## Hierarchy + +- **`Pinia`** + + ↳ [`TestingPinia`](pinia_testing.TestingPinia.md) + +## Properties + +### install + +• **install**: (`app`: `App`<`any`\>) => `void` + +#### Type declaration + +▸ (`app`): `void` + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `app` | `App`<`any`\> | + +##### Returns + +`void` + +___ + +### state + +• **state**: `Ref`<`Record`<`string`, [`StateTree`](../modules/pinia.md#statetree)\>\> + +root state + +## Methods + +### use + +▸ **use**(`plugin`): [`Pinia`](pinia.Pinia.md) + +Adds a store plugin to extend every store + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `plugin` | [`PiniaPlugin`](pinia.PiniaPlugin.md) | store plugin to add | + +#### Returns + +[`Pinia`](pinia.Pinia.md) diff --git a/packages/docs-new/api/interfaces/pinia.PiniaCustomProperties.md b/packages/docs-new/api/interfaces/pinia.PiniaCustomProperties.md new file mode 100644 index 00000000..46c96d5e --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.PiniaCustomProperties.md @@ -0,0 +1,44 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / PiniaCustomProperties + +# Interface: PiniaCustomProperties + +[pinia](../modules/pinia.md).PiniaCustomProperties + +Interface to be extended by the user when they add properties through plugins. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` = `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | +| `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | +| `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | + +## Accessors + +### route + +• `get` **route**(): `RouteLocationNormalized` + +#### Returns + +`RouteLocationNormalized` + +• `set` **route**(`value`): `void` + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `value` | `RouteLocationNormalizedLoaded` \| `Ref`<`RouteLocationNormalizedLoaded`\> | + +#### Returns + +`void` diff --git a/packages/docs-new/api/interfaces/pinia.PiniaCustomStateProperties.md b/packages/docs-new/api/interfaces/pinia.PiniaCustomStateProperties.md new file mode 100644 index 00000000..adc85330 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.PiniaCustomStateProperties.md @@ -0,0 +1,19 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / PiniaCustomStateProperties + +# Interface: PiniaCustomStateProperties + +[pinia](../modules/pinia.md).PiniaCustomStateProperties + +Properties that are added to every `store.$state` by `pinia.use()`. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | diff --git a/packages/docs-new/api/interfaces/pinia.PiniaPlugin.md b/packages/docs-new/api/interfaces/pinia.PiniaPlugin.md new file mode 100644 index 00000000..a7454493 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.PiniaPlugin.md @@ -0,0 +1,30 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / PiniaPlugin + +# Interface: PiniaPlugin + +[pinia](../modules/pinia.md).PiniaPlugin + +## Callable + +### PiniaPlugin + +▸ **PiniaPlugin**(`context`): `void` \| `Partial`<[`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> & [`PiniaCustomStateProperties`](pinia.PiniaCustomStateProperties.md)<[`StateTree`](../modules/pinia.md#statetree)\>\> + +Plugin to extend every store. Returns an object to extend the store or +nothing. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `context` | [`PiniaPluginContext`](pinia.PiniaPluginContext.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> | Context | + +#### Returns + +`void` \| `Partial`<[`PiniaCustomProperties`](pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](../modules/pinia.md#statetree), [`_GettersTree`](../modules/pinia.md#_getterstree)<[`StateTree`](../modules/pinia.md#statetree)\>, [`_ActionsTree`](../modules/pinia.md#_actionstree)\> & [`PiniaCustomStateProperties`](pinia.PiniaCustomStateProperties.md)<[`StateTree`](../modules/pinia.md#statetree)\>\> diff --git a/packages/docs-new/api/interfaces/pinia.PiniaPluginContext.md b/packages/docs-new/api/interfaces/pinia.PiniaPluginContext.md new file mode 100644 index 00000000..84c07ed3 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.PiniaPluginContext.md @@ -0,0 +1,54 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / PiniaPluginContext + +# Interface: PiniaPluginContext + +[pinia](../modules/pinia.md).PiniaPluginContext + +Context argument passed to Pinia plugins. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` = `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | +| `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | +| `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | + +## Properties + +### app + +• **app**: `App`<`any`\> + +Current app created with `Vue.createApp()`. + +___ + +### options + +• **options**: [`DefineStoreOptionsInPlugin`](pinia.DefineStoreOptionsInPlugin.md)<`Id`, `S`, `G`, `A`\> + +Initial options defining the store when calling `defineStore()`. + +___ + +### pinia + +• **pinia**: [`Pinia`](pinia.Pinia.md) + +pinia instance. + +___ + +### store + +• **store**: [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> + +Current store being extended. diff --git a/packages/docs-new/api/interfaces/pinia.StoreDefinition.md b/packages/docs-new/api/interfaces/pinia.StoreDefinition.md new file mode 100644 index 00000000..9288199f --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.StoreDefinition.md @@ -0,0 +1,47 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / StoreDefinition + +# Interface: StoreDefinition + +[pinia](../modules/pinia.md).StoreDefinition + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` = `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) = [`StateTree`](../modules/pinia.md#statetree) | +| `G` | [`_GettersTree`](../modules/pinia.md#_getterstree)<`S`\> | +| `A` | [`_ActionsTree`](../modules/pinia.md#_actionstree) | + +## Callable + +### StoreDefinition + +▸ **StoreDefinition**(`pinia?`, `hot?`): [`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> + +Returns a store, creates it if necessary. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pinia?` | ``null`` \| [`Pinia`](pinia.Pinia.md) | Pinia instance to retrieve the store | +| `hot?` | [`StoreGeneric`](../modules/pinia.md#storegeneric) | dev only hot module replacement | + +#### Returns + +[`Store`](../modules/pinia.md#store)<`Id`, `S`, `G`, `A`\> + +## Properties + +### $id + +• **$id**: `Id` + +Id of the store. Used by map helpers. diff --git a/packages/docs-new/api/interfaces/pinia.StoreProperties.md b/packages/docs-new/api/interfaces/pinia.StoreProperties.md new file mode 100644 index 00000000..416136de --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.StoreProperties.md @@ -0,0 +1,43 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / StoreProperties + +# Interface: StoreProperties + +[pinia](../modules/pinia.md).StoreProperties + +Properties of a store. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | + +## Hierarchy + +- **`StoreProperties`** + + ↳ [`_StoreWithState`](pinia._StoreWithState.md) + +## Properties + +### $id + +• **$id**: `Id` + +Unique identifier of the store + +___ + +### \_customProperties + +• **\_customProperties**: `Set`<`string`\> + +Used by devtools plugin to retrieve properties added with plugins. Removed +in production. Can be used by the user to add property keys of the store +that should be displayed in devtools. diff --git a/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationDirect.md b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationDirect.md new file mode 100644 index 00000000..e73d9329 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationDirect.md @@ -0,0 +1,59 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationDirect + +# Interface: SubscriptionCallbackMutationDirect + +[pinia](../modules/pinia.md).SubscriptionCallbackMutationDirect + +Context passed to a subscription callback when directly mutating the state of +a store with `store.someState = newValue` or `store.$state.someState = +newValue`. + +## Hierarchy + +- [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) + + ↳ **`SubscriptionCallbackMutationDirect`** + +## Properties + +### events + +• **events**: `DebuggerEvent` + +🔴 DEV ONLY, DO NOT use for production code. Different mutation calls. Comes from +https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging and allows to track mutations in +devtools and plugins **during development only**. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[events](pinia._SubscriptionCallbackMutationBase.md#events) + +___ + +### storeId + +• **storeId**: `string` + +`id` of the store doing the mutation. + +#### Inherited from + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) + +___ + +### type + +• **type**: [`direct`](../enums/pinia.MutationType.md#direct) + +Type of the mutation. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) diff --git a/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md new file mode 100644 index 00000000..b70a236b --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md @@ -0,0 +1,58 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationPatchFunction + +# Interface: SubscriptionCallbackMutationPatchFunction + +[pinia](../modules/pinia.md).SubscriptionCallbackMutationPatchFunction + +Context passed to a subscription callback when `store.$patch()` is called +with a function. + +## Hierarchy + +- [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) + + ↳ **`SubscriptionCallbackMutationPatchFunction`** + +## Properties + +### events + +• **events**: `DebuggerEvent`[] + +🔴 DEV ONLY, DO NOT use for production code. Different mutation calls. Comes from +https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging and allows to track mutations in +devtools and plugins **during development only**. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[events](pinia._SubscriptionCallbackMutationBase.md#events) + +___ + +### storeId + +• **storeId**: `string` + +`id` of the store doing the mutation. + +#### Inherited from + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) + +___ + +### type + +• **type**: [`patchFunction`](../enums/pinia.MutationType.md#patchfunction) + +Type of the mutation. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) diff --git a/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchObject.md b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchObject.md new file mode 100644 index 00000000..37685c90 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia.SubscriptionCallbackMutationPatchObject.md @@ -0,0 +1,72 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / SubscriptionCallbackMutationPatchObject + +# Interface: SubscriptionCallbackMutationPatchObject + +[pinia](../modules/pinia.md).SubscriptionCallbackMutationPatchObject + +Context passed to a subscription callback when `store.$patch()` is called +with an object. + +## Type parameters + +| Name | +| :------ | +| `S` | + +## Hierarchy + +- [`_SubscriptionCallbackMutationBase`](pinia._SubscriptionCallbackMutationBase.md) + + ↳ **`SubscriptionCallbackMutationPatchObject`** + +## Properties + +### events + +• **events**: `DebuggerEvent`[] + +🔴 DEV ONLY, DO NOT use for production code. Different mutation calls. Comes from +https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging and allows to track mutations in +devtools and plugins **during development only**. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[events](pinia._SubscriptionCallbackMutationBase.md#events) + +___ + +### payload + +• **payload**: [`_DeepPartial`](../modules/pinia.md#_deeppartial)<`S`\> + +Object passed to `store.$patch()`. + +___ + +### storeId + +• **storeId**: `string` + +`id` of the store doing the mutation. + +#### Inherited from + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[storeId](pinia._SubscriptionCallbackMutationBase.md#storeid) + +___ + +### type + +• **type**: [`patchObject`](../enums/pinia.MutationType.md#patchobject) + +Type of the mutation. + +#### Overrides + +[_SubscriptionCallbackMutationBase](pinia._SubscriptionCallbackMutationBase.md).[type](pinia._SubscriptionCallbackMutationBase.md#type) diff --git a/packages/docs-new/api/interfaces/pinia._StoreOnActionListenerContext.md b/packages/docs-new/api/interfaces/pinia._StoreOnActionListenerContext.md new file mode 100644 index 00000000..119db171 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia._StoreOnActionListenerContext.md @@ -0,0 +1,93 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / \_StoreOnActionListenerContext + +# Interface: \_StoreOnActionListenerContext + +[pinia](../modules/pinia.md)._StoreOnActionListenerContext + +Actual type for [StoreOnActionListenerContext](../modules/pinia.md#storeonactionlistenercontext). Exists for refactoring +purposes. For internal use only. +For internal use **only** + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Store` | `Store` | +| `ActionName` | extends `string` | +| `A` | `A` | + +## Properties + +### after + +• **after**: (`callback`: `A` extends `Record`<`ActionName`, [`_Method`](../modules/pinia.md#_method)\> ? (`resolvedReturn`: [`_Awaited`](../modules/pinia.md#_awaited)<`ReturnType`<`A`[`ActionName`]\>\>) => `void` : () => `void`) => `void` + +#### Type declaration + +▸ (`callback`): `void` + +Sets up a hook once the action is finished. It receives the return value +of the action, if it's a Promise, it will be unwrapped. + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `callback` | `A` extends `Record`<`ActionName`, [`_Method`](../modules/pinia.md#_method)\> ? (`resolvedReturn`: [`_Awaited`](../modules/pinia.md#_awaited)<`ReturnType`<`A`[`ActionName`]\>\>) => `void` : () => `void` | + +##### Returns + +`void` + +___ + +### args + +• **args**: `A` extends `Record`<`ActionName`, [`_Method`](../modules/pinia.md#_method)\> ? `Parameters`<`A`[`ActionName`]\> : `unknown`[] + +Parameters passed to the action + +___ + +### name + +• **name**: `ActionName` + +Name of the action + +___ + +### onError + +• **onError**: (`callback`: (`error`: `unknown`) => `void`) => `void` + +#### Type declaration + +▸ (`callback`): `void` + +Sets up a hook if the action fails. Return `false` to catch the error and +stop it from propagating. + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `callback` | (`error`: `unknown`) => `void` | + +##### Returns + +`void` + +___ + +### store + +• **store**: `Store` + +Store that is invoking the action diff --git a/packages/docs-new/api/interfaces/pinia._StoreWithState.md b/packages/docs-new/api/interfaces/pinia._StoreWithState.md new file mode 100644 index 00000000..3cd030b4 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia._StoreWithState.md @@ -0,0 +1,253 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / \_StoreWithState + +# Interface: \_StoreWithState + +[pinia](../modules/pinia.md)._StoreWithState + +Base store with state and functions. Should not be used directly. + +## Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](../modules/pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +## Hierarchy + +- [`StoreProperties`](pinia.StoreProperties.md)<`Id`\> + + ↳ **`_StoreWithState`** + +## Properties + +### $id + +• **$id**: `Id` + +Unique identifier of the store + +#### Inherited from + +[StoreProperties](pinia.StoreProperties.md).[$id](pinia.StoreProperties.md#$id) + +___ + +### $state + +• **$state**: `UnwrapRef`<`S`\> & [`PiniaCustomStateProperties`](pinia.PiniaCustomStateProperties.md)<`S`\> + +State of the Store. Setting it will replace the whole state. + +___ + +### \_customProperties + +• **\_customProperties**: `Set`<`string`\> + +Used by devtools plugin to retrieve properties added with plugins. Removed +in production. Can be used by the user to add property keys of the store +that should be displayed in devtools. + +#### Inherited from + +[StoreProperties](pinia.StoreProperties.md).[_customProperties](pinia.StoreProperties.md#_customproperties) + +## Methods + +### $dispose + +▸ **$dispose**(): `void` + +Stops the associated effect scope of the store and remove it from the store +registry. Plugins can override this method to cleanup any added effects. +e.g. devtools plugin stops displaying disposed stores from devtools. + +#### Returns + +`void` + +___ + +### $onAction + +▸ **$onAction**(`callback`, `detached?`): () => `void` + +Setups a callback to be called every time an action is about to get +invoked. The callback receives an object with all the relevant information +of the invoked action: +- `store`: the store it is invoked on +- `name`: The name of the action +- `args`: The parameters passed to the action + +On top of these, it receives two functions that allow setting up a callback +once the action finishes or when it fails. + +It also returns a function to remove the callback. Note than when calling +`store.$onAction()` inside of a component, it will be automatically cleaned +up when the component gets unmounted unless `detached` is set to true. + +**`Example`** + +```js +store.$onAction(({ after, onError }) => { + // Here you could share variables between all of the hooks as well as + // setting up watchers and clean them up + after((resolvedValue) => { + // can be used to cleanup side effects +. // `resolvedValue` is the value returned by the action, if it's a +. // Promise, it will be the resolved value instead of the Promise + }) + onError((error) => { + // can be used to pass up errors + }) +}) +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `callback` | [`StoreOnActionListener`](../modules/pinia.md#storeonactionlistener)<`Id`, `S`, `G`, `A`\> | callback called before every action | +| `detached?` | `boolean` | detach the subscription from the context this is called from | + +#### Returns + +`fn` + +function that removes the watcher + +▸ (): `void` + +Setups a callback to be called every time an action is about to get +invoked. The callback receives an object with all the relevant information +of the invoked action: +- `store`: the store it is invoked on +- `name`: The name of the action +- `args`: The parameters passed to the action + +On top of these, it receives two functions that allow setting up a callback +once the action finishes or when it fails. + +It also returns a function to remove the callback. Note than when calling +`store.$onAction()` inside of a component, it will be automatically cleaned +up when the component gets unmounted unless `detached` is set to true. + +**`Example`** + +```js +store.$onAction(({ after, onError }) => { + // Here you could share variables between all of the hooks as well as + // setting up watchers and clean them up + after((resolvedValue) => { + // can be used to cleanup side effects +. // `resolvedValue` is the value returned by the action, if it's a +. // Promise, it will be the resolved value instead of the Promise + }) + onError((error) => { + // can be used to pass up errors + }) +}) +``` + +##### Returns + +`void` + +function that removes the watcher + +___ + +### $patch + +▸ **$patch**(`partialState`): `void` + +Applies a state patch to current state. Allows passing nested values + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `partialState` | [`_DeepPartial`](../modules/pinia.md#_deeppartial)<`UnwrapRef`<`S`\>\> | patch to apply to the state | + +#### Returns + +`void` + +▸ **$patch**<`F`\>(`stateMutator`): `void` + +Group multiple changes into one function. Useful when mutating objects like +Sets or arrays and applying an object patch isn't practical, e.g. appending +to an array. The function passed to `$patch()` **must be synchronous**. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `F` | extends (`state`: `UnwrapRef`<`S`\>) => `any` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `stateMutator` | `ReturnType`<`F`\> extends `Promise`<`any`\> ? `never` : `F` | function that mutates `state`, cannot be async | + +#### Returns + +`void` + +___ + +### $reset + +▸ **$reset**(): `void` + +Resets the store to its initial state by building a new state object. +TODO: make this options only + +#### Returns + +`void` + +___ + +### $subscribe + +▸ **$subscribe**(`callback`, `options?`): () => `void` + +Setups a callback to be called whenever the state changes. It also returns a function to remove the callback. Note +that when calling `store.$subscribe()` inside of a component, it will be automatically cleaned up when the +component gets unmounted unless `detached` is set to true. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `callback` | [`SubscriptionCallback`](../modules/pinia.md#subscriptioncallback)<`S`\> | callback passed to the watcher | +| `options?` | { `detached?`: `boolean` } & `WatchOptions`<`boolean`\> | `watch` options + `detached` to detach the subscription from the context (usually a component) this is called from. Note that the `flush` option does not affect calls to `store.$patch()`. | + +#### Returns + +`fn` + +function that removes the watcher + +▸ (): `void` + +Setups a callback to be called whenever the state changes. It also returns a function to remove the callback. Note +that when calling `store.$subscribe()` inside of a component, it will be automatically cleaned up when the +component gets unmounted unless `detached` is set to true. + +##### Returns + +`void` + +function that removes the watcher diff --git a/packages/docs-new/api/interfaces/pinia._SubscriptionCallbackMutationBase.md b/packages/docs-new/api/interfaces/pinia._SubscriptionCallbackMutationBase.md new file mode 100644 index 00000000..e127b13e --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia._SubscriptionCallbackMutationBase.md @@ -0,0 +1,49 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [pinia](../modules/pinia.md) / \_SubscriptionCallbackMutationBase + +# Interface: \_SubscriptionCallbackMutationBase + +[pinia](../modules/pinia.md)._SubscriptionCallbackMutationBase + +Base type for the context passed to a subscription callback. Internal type. + +## Hierarchy + +- **`_SubscriptionCallbackMutationBase`** + + ↳ [`SubscriptionCallbackMutationDirect`](pinia.SubscriptionCallbackMutationDirect.md) + + ↳ [`SubscriptionCallbackMutationPatchFunction`](pinia.SubscriptionCallbackMutationPatchFunction.md) + + ↳ [`SubscriptionCallbackMutationPatchObject`](pinia.SubscriptionCallbackMutationPatchObject.md) + +## Properties + +### events + +• `Optional` **events**: `DebuggerEvent` \| `DebuggerEvent`[] + +🔴 DEV ONLY, DO NOT use for production code. Different mutation calls. Comes from +https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging and allows to track mutations in +devtools and plugins **during development only**. + +___ + +### storeId + +• **storeId**: `string` + +`id` of the store doing the mutation. + +___ + +### type + +• **type**: [`MutationType`](../enums/pinia.MutationType.md) + +Type of the mutation. diff --git a/packages/docs-new/api/interfaces/pinia_nuxt.ModuleOptions.md b/packages/docs-new/api/interfaces/pinia_nuxt.ModuleOptions.md new file mode 100644 index 00000000..0cf62ca8 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia_nuxt.ModuleOptions.md @@ -0,0 +1,43 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [@pinia/nuxt](../modules/pinia_nuxt.md) / ModuleOptions + +# Interface: ModuleOptions + +[@pinia/nuxt](../modules/pinia_nuxt.md).ModuleOptions + +## Properties + +### autoImports + +• `Optional` **autoImports**: (`string` \| [`string`, `string`])[] + +Array of auto imports to be added to the nuxt.config.js file. + +**`Example`** + +```js +autoImports: [ + // automatically import `defineStore` + 'defineStore', + // automatically import `defineStore` as `definePiniaStore` + ['defineStore', 'definePiniaStore', +] +``` + +___ + +### disableVuex + +• `Optional` **disableVuex**: `boolean` + +Pinia disables Vuex by default, set this option to `false` to avoid it and +use Pinia alongside Vuex (Nuxt 2 only) + +**`Default`** + +`true` diff --git a/packages/docs-new/api/interfaces/pinia_testing.TestingOptions.md b/packages/docs-new/api/interfaces/pinia_testing.TestingOptions.md new file mode 100644 index 00000000..8c77dcba --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia_testing.TestingOptions.md @@ -0,0 +1,97 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [@pinia/testing](../modules/pinia_testing.md) / TestingOptions + +# Interface: TestingOptions + +[@pinia/testing](../modules/pinia_testing.md).TestingOptions + +## Properties + +### createSpy + +• `Optional` **createSpy**: (`fn?`: (...`args`: `any`[]) => `any`) => (...`args`: `any`[]) => `any` + +#### Type declaration + +▸ (`fn?`): (...`args`: `any`[]) => `any` + +Function used to create a spy for actions and `$patch()`. Pre-configured +with `jest.fn()` in jest projects or `vi.fn()` in vitest projects. + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `fn?` | (...`args`: `any`[]) => `any` | + +##### Returns + +`fn` + +▸ (...`args`): `any` + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `...args` | `any`[] | + +##### Returns + +`any` + +___ + +### fakeApp + +• `Optional` **fakeApp**: `boolean` + +Creates an empty App and calls `app.use(pinia)` with the created testing +pinia. This is allows you to use plugins while unit testing stores as +plugins **will wait for pinia to be installed in order to be executed**. +Defaults to false. + +___ + +### initialState + +• `Optional` **initialState**: [`StateTree`](../modules/pinia.md#statetree) + +Allows defining a partial initial state of all your stores. This state gets applied after a store is created, +allowing you to only set a few properties that are required in your test. + +___ + +### plugins + +• `Optional` **plugins**: [`PiniaPlugin`](pinia.PiniaPlugin.md)[] + +Plugins to be installed before the testing plugin. Add any plugins used in +your application that will be used while testing. + +___ + +### stubActions + +• `Optional` **stubActions**: `boolean` + +When set to false, actions are only spied, they still get executed. When +set to true, actions will be replaced with spies, resulting in their code +not being executed. Defaults to true. NOTE: when providing `createSpy()`, +it will **only** make the `fn` argument `undefined`. You still have to +handle this in `createSpy()`. + +___ + +### stubPatch + +• `Optional` **stubPatch**: `boolean` + +When set to true, calls to `$patch()` won't change the state. Defaults to +false. NOTE: when providing `createSpy()`, it will **only** make the `fn` +argument `undefined`. You still have to handle this in `createSpy()`. diff --git a/packages/docs-new/api/interfaces/pinia_testing.TestingPinia.md b/packages/docs-new/api/interfaces/pinia_testing.TestingPinia.md new file mode 100644 index 00000000..670c97d1 --- /dev/null +++ b/packages/docs-new/api/interfaces/pinia_testing.TestingPinia.md @@ -0,0 +1,86 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / [@pinia/testing](../modules/pinia_testing.md) / TestingPinia + +# Interface: TestingPinia + +[@pinia/testing](../modules/pinia_testing.md).TestingPinia + +Pinia instance specifically designed for testing. Extends a regular +`Pinia` instance with test specific properties. + +## Hierarchy + +- [`Pinia`](pinia.Pinia.md) + + ↳ **`TestingPinia`** + +## Properties + +### app + +• **app**: `App`<`any`\> + +App used by Pinia + +___ + +### install + +• **install**: (`app`: `App`<`any`\>) => `void` + +#### Type declaration + +▸ (`app`): `void` + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `app` | `App`<`any`\> | + +##### Returns + +`void` + +#### Inherited from + +[Pinia](pinia.Pinia.md).[install](pinia.Pinia.md#install) + +___ + +### state + +• **state**: `Ref`<`Record`<`string`, [`StateTree`](../modules/pinia.md#statetree)\>\> + +root state + +#### Inherited from + +[Pinia](pinia.Pinia.md).[state](pinia.Pinia.md#state) + +## Methods + +### use + +▸ **use**(`plugin`): [`Pinia`](pinia.Pinia.md) + +Adds a store plugin to extend every store + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `plugin` | [`PiniaPlugin`](pinia.PiniaPlugin.md) | store plugin to add | + +#### Returns + +[`Pinia`](pinia.Pinia.md) + +#### Inherited from + +[Pinia](pinia.Pinia.md).[use](pinia.Pinia.md#use) diff --git a/packages/docs-new/api/modules/pinia.md b/packages/docs-new/api/modules/pinia.md new file mode 100644 index 00000000..e1ec6d36 --- /dev/null +++ b/packages/docs-new/api/modules/pinia.md @@ -0,0 +1,1162 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / pinia + +# Module: pinia + +## Enumerations + +- [MutationType](../enums/pinia.MutationType.md) + +## Interfaces + +- [DefineSetupStoreOptions](../interfaces/pinia.DefineSetupStoreOptions.md) +- [DefineStoreOptions](../interfaces/pinia.DefineStoreOptions.md) +- [DefineStoreOptionsBase](../interfaces/pinia.DefineStoreOptionsBase.md) +- [DefineStoreOptionsInPlugin](../interfaces/pinia.DefineStoreOptionsInPlugin.md) +- [MapStoresCustomization](../interfaces/pinia.MapStoresCustomization.md) +- [Pinia](../interfaces/pinia.Pinia.md) +- [PiniaCustomProperties](../interfaces/pinia.PiniaCustomProperties.md) +- [PiniaCustomStateProperties](../interfaces/pinia.PiniaCustomStateProperties.md) +- [PiniaPlugin](../interfaces/pinia.PiniaPlugin.md) +- [PiniaPluginContext](../interfaces/pinia.PiniaPluginContext.md) +- [StoreDefinition](../interfaces/pinia.StoreDefinition.md) +- [StoreProperties](../interfaces/pinia.StoreProperties.md) +- [SubscriptionCallbackMutationDirect](../interfaces/pinia.SubscriptionCallbackMutationDirect.md) +- [SubscriptionCallbackMutationPatchFunction](../interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md) +- [SubscriptionCallbackMutationPatchObject](../interfaces/pinia.SubscriptionCallbackMutationPatchObject.md) +- [\_StoreOnActionListenerContext](../interfaces/pinia._StoreOnActionListenerContext.md) +- [\_StoreWithState](../interfaces/pinia._StoreWithState.md) +- [\_SubscriptionCallbackMutationBase](../interfaces/pinia._SubscriptionCallbackMutationBase.md) + +## Type Aliases + +### PiniaStorePlugin + +Ƭ **PiniaStorePlugin**: [`PiniaPlugin`](../interfaces/pinia.PiniaPlugin.md) + +Plugin to extend every store. + +**`Deprecated`** + +use PiniaPlugin instead + +___ + +### StateTree + +Ƭ **StateTree**: `Record`<`string` \| `number` \| `symbol`, `any`\> + +Generic state of a Store + +___ + +### Store + +Ƭ **Store**<`Id`, `S`, `G`, `A`\>: [`_StoreWithState`](../interfaces/pinia._StoreWithState.md)<`Id`, `S`, `G`, `A`\> & `UnwrapRef`<`S`\> & [`_StoreWithGetters`](pinia.md#_storewithgetters)<`G`\> & [`_ActionsTree`](pinia.md#_actionstree) extends `A` ? {} : `A` & [`PiniaCustomProperties`](../interfaces/pinia.PiniaCustomProperties.md)<`Id`, `S`, `G`, `A`\> & [`PiniaCustomStateProperties`](../interfaces/pinia.PiniaCustomStateProperties.md)<`S`\> + +Store type to build a store. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` = `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) = {} | +| `G` | {} | +| `A` | {} | + +___ + +### StoreActions + +Ƭ **StoreActions**<`SS`\>: `SS` extends [`Store`](pinia.md#store)<`string`, [`StateTree`](pinia.md#statetree), [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, infer A\> ? `A` : [`_ExtractActionsFromSetupStore`](pinia.md#_extractactionsfromsetupstore)<`SS`\> + +Extract the actions of a store type. Works with both a Setup Store or an +Options Store. + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### StoreGeneric + +Ƭ **StoreGeneric**: [`Store`](pinia.md#store)<`string`, [`StateTree`](pinia.md#statetree), [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, [`_ActionsTree`](pinia.md#_actionstree)\> + +Generic and type-unsafe version of Store. Doesn't fail on access with +strings, making it much easier to write generic functions that do not care +about the kind of store that is passed. + +___ + +### StoreGetters + +Ƭ **StoreGetters**<`SS`\>: `SS` extends [`Store`](pinia.md#store)<`string`, [`StateTree`](pinia.md#statetree), infer G, [`_ActionsTree`](pinia.md#_actionstree)\> ? [`_StoreWithGetters`](pinia.md#_storewithgetters)<`G`\> : [`_ExtractGettersFromSetupStore`](pinia.md#_extractgettersfromsetupstore)<`SS`\> + +Extract the getters of a store type. Works with both a Setup Store or an +Options Store. + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### StoreOnActionListener + +Ƭ **StoreOnActionListener**<`Id`, `S`, `G`, `A`\>: (`context`: [`StoreOnActionListenerContext`](pinia.md#storeonactionlistenercontext)<`Id`, `S`, `G`, {} extends `A` ? [`_ActionsTree`](pinia.md#_actionstree) : `A`\>) => `void` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +#### Type declaration + +▸ (`context`): `void` + +Argument of `store.$onAction()` + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `context` | [`StoreOnActionListenerContext`](pinia.md#storeonactionlistenercontext)<`Id`, `S`, `G`, {} extends `A` ? [`_ActionsTree`](pinia.md#_actionstree) : `A`\> | + +##### Returns + +`void` + +___ + +### StoreOnActionListenerContext + +Ƭ **StoreOnActionListenerContext**<`Id`, `S`, `G`, `A`\>: [`_ActionsTree`](pinia.md#_actionstree) extends `A` ? [`_StoreOnActionListenerContext`](../interfaces/pinia._StoreOnActionListenerContext.md)<[`StoreGeneric`](pinia.md#storegeneric), `string`, [`_ActionsTree`](pinia.md#_actionstree)\> : { [Name in keyof A]: Name extends string ? \_StoreOnActionListenerContext, Name, A\> : never }[keyof `A`] + +Context object passed to callbacks of `store.$onAction(context => {})` +TODO: should have only the Id, the Store and Actions to generate the proper object + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | `G` | +| `A` | `A` | + +___ + +### StoreState + +Ƭ **StoreState**<`SS`\>: `SS` extends [`Store`](pinia.md#store)<`string`, infer S, [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, [`_ActionsTree`](pinia.md#_actionstree)\> ? `UnwrapRef`<`S`\> : [`_ExtractStateFromSetupStore`](pinia.md#_extractstatefromsetupstore)<`SS`\> + +Extract the state of a store type. Works with both a Setup Store or an +Options Store. Note this unwraps refs. + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### SubscriptionCallback + +Ƭ **SubscriptionCallback**<`S`\>: (`mutation`: [`SubscriptionCallbackMutation`](pinia.md#subscriptioncallbackmutation)<`S`\>, `state`: `UnwrapRef`<`S`\>) => `void` + +#### Type parameters + +| Name | +| :------ | +| `S` | + +#### Type declaration + +▸ (`mutation`, `state`): `void` + +Callback of a subscription + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `mutation` | [`SubscriptionCallbackMutation`](pinia.md#subscriptioncallbackmutation)<`S`\> | +| `state` | `UnwrapRef`<`S`\> | + +##### Returns + +`void` + +___ + +### SubscriptionCallbackMutation + +Ƭ **SubscriptionCallbackMutation**<`S`\>: [`SubscriptionCallbackMutationDirect`](../interfaces/pinia.SubscriptionCallbackMutationDirect.md) \| [`SubscriptionCallbackMutationPatchObject`](../interfaces/pinia.SubscriptionCallbackMutationPatchObject.md)<`S`\> \| [`SubscriptionCallbackMutationPatchFunction`](../interfaces/pinia.SubscriptionCallbackMutationPatchFunction.md) + +Context object passed to a subscription callback. + +#### Type parameters + +| Name | +| :------ | +| `S` | + +___ + +### \_ActionsTree + +Ƭ **\_ActionsTree**: `Record`<`string`, [`_Method`](pinia.md#_method)\> + +Type of an object of Actions. For internal usage only. +For internal use **only** + +___ + +### \_Awaited + +Ƭ **\_Awaited**<`T`\>: `T` extends ``null`` \| `undefined` ? `T` : `T` extends `object` & { `then`: (`onfulfilled`: `F`) => `any` } ? `F` extends (`value`: infer V, ...`args`: `any`) => `any` ? [`_Awaited`](pinia.md#_awaited)<`V`\> : `never` : `T` + +#### Type parameters + +| Name | +| :------ | +| `T` | + +___ + +### \_DeepPartial + +Ƭ **\_DeepPartial**<`T`\>: { [K in keyof T]?: \_DeepPartial } + +Recursive `Partial`. Used by [['$patch']](pinia.md#store). + +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `T` | + +___ + +### \_ExtractActionsFromSetupStore + +Ƭ **\_ExtractActionsFromSetupStore**<`SS`\>: `SS` extends `undefined` \| `void` ? {} : [`_ExtractActionsFromSetupStore_Keys`](pinia.md#_extractactionsfromsetupstore_keys)<`SS`\> extends keyof `SS` ? `Pick`<`SS`, [`_ExtractActionsFromSetupStore_Keys`](pinia.md#_extractactionsfromsetupstore_keys)<`SS`\>\> : `never` + +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_ExtractActionsFromSetupStore\_Keys + +Ƭ **\_ExtractActionsFromSetupStore\_Keys**<`SS`\>: keyof { [K in keyof SS as SS[K] extends \_Method ? K : never]: any } + +Type that enables refactoring through IDE. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_ExtractGettersFromSetupStore + +Ƭ **\_ExtractGettersFromSetupStore**<`SS`\>: `SS` extends `undefined` \| `void` ? {} : [`_ExtractGettersFromSetupStore_Keys`](pinia.md#_extractgettersfromsetupstore_keys)<`SS`\> extends keyof `SS` ? `Pick`<`SS`, [`_ExtractGettersFromSetupStore_Keys`](pinia.md#_extractgettersfromsetupstore_keys)<`SS`\>\> : `never` + +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_ExtractGettersFromSetupStore\_Keys + +Ƭ **\_ExtractGettersFromSetupStore\_Keys**<`SS`\>: keyof { [K in keyof SS as SS[K] extends ComputedRef ? K : never]: any } + +Type that enables refactoring through IDE. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_ExtractStateFromSetupStore + +Ƭ **\_ExtractStateFromSetupStore**<`SS`\>: `SS` extends `undefined` \| `void` ? {} : [`_ExtractStateFromSetupStore_Keys`](pinia.md#_extractstatefromsetupstore_keys)<`SS`\> extends keyof `SS` ? [`_UnwrapAll`](pinia.md#_unwrapall)<`Pick`<`SS`, [`_ExtractStateFromSetupStore_Keys`](pinia.md#_extractstatefromsetupstore_keys)<`SS`\>\>\> : `never` + +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_ExtractStateFromSetupStore\_Keys + +Ƭ **\_ExtractStateFromSetupStore\_Keys**<`SS`\>: keyof { [K in keyof SS as SS[K] extends \_Method \| ComputedRef ? never : K]: any } + +Type that enables refactoring through IDE. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +___ + +### \_GettersTree + +Ƭ **\_GettersTree**<`S`\>: `Record`<`string`, (`state`: `UnwrapRef`<`S`\> & `UnwrapRef`<[`PiniaCustomStateProperties`](../interfaces/pinia.PiniaCustomStateProperties.md)<`S`\>\>) => `any` \| () => `any`\> + +Type of an object of Getters that infers the argument. For internal usage only. +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](pinia.md#statetree) | + +___ + +### \_MapActionsObjectReturn + +Ƭ **\_MapActionsObjectReturn**<`A`, `T`\>: { [key in keyof T]: A[T[key]] } + +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `A` | `A` | +| `T` | extends `Record`<`string`, keyof `A`\> | + +___ + +### \_MapActionsReturn + +Ƭ **\_MapActionsReturn**<`A`\>: { [key in keyof A]: A[key] } + +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `A` | + +___ + +### \_MapStateObjectReturn + +Ƭ **\_MapStateObjectReturn**<`Id`, `S`, `G`, `A`, `T`\>: { [key in keyof T]: Function } + +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `T` | extends `Record`<`string`, keyof `S` \| keyof `G` \| (`store`: [`Store`](pinia.md#store)<`Id`, `S`, `G`, `A`\>) => `any`\> = {} | + +___ + +### \_MapStateReturn + +Ƭ **\_MapStateReturn**<`S`, `G`, `Keys`\>: { [key in Keys]: Function } + +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `Keys` | extends keyof `S` \| keyof `G` = keyof `S` \| keyof `G` | + +___ + +### \_MapWritableStateObjectReturn + +Ƭ **\_MapWritableStateObjectReturn**<`S`, `T`\>: { [key in keyof T]: Object } + +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `T` | extends `Record`<`string`, keyof `S`\> | + +___ + +### \_MapWritableStateReturn + +Ƭ **\_MapWritableStateReturn**<`S`\>: { [key in keyof S]: Object } + +For internal use **only** + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `S` | extends [`StateTree`](pinia.md#statetree) | + +___ + +### \_Method + +Ƭ **\_Method**: (...`args`: `any`[]) => `any` + +#### Type declaration + +▸ (...`args`): `any` + +Generic type for a function that can infer arguments and return type + +For internal use **only** + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `...args` | `any`[] | + +##### Returns + +`any` + +___ + +### \_Spread + +Ƭ **\_Spread**<`A`\>: `A` extends [infer L, ...(infer R)] ? [`_StoreObject`](pinia.md#_storeobject)<`L`\> & [`_Spread`](pinia.md#_spread)<`R`\> : `unknown` + +For internal use **only**. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `A` | extends readonly `any`[] | + +___ + +### \_StoreObject + +Ƭ **\_StoreObject**<`S`\>: `S` extends [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md) ? { [Id in \`${Ids}${MapStoresCustomization extends Record<"suffix", infer Suffix extends string\> ? Suffix : "Store"}\`]: Function } : {} + +For internal use **only**. + +#### Type parameters + +| Name | +| :------ | +| `S` | + +___ + +### \_StoreWithActions + +Ƭ **\_StoreWithActions**<`A`\>: { [k in keyof A]: A[k] extends Function ? Function : never } + +Store augmented for actions. For internal usage only. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `A` | + +___ + +### \_StoreWithGetters + +Ƭ **\_StoreWithGetters**<`G`\>: { readonly [k in keyof G]: G[k] extends Function ? R : UnwrapRef } + +Store augmented with getters. For internal usage only. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `G` | + +___ + +### \_UnwrapAll + +Ƭ **\_UnwrapAll**<`SS`\>: { [K in keyof SS]: UnwrapRef } + +Type that enables refactoring through IDE. +For internal use **only** + +#### Type parameters + +| Name | +| :------ | +| `SS` | + +## Variables + +### PiniaVuePlugin + +• `Const` **PiniaVuePlugin**: `Plugin` + +Vue 2 Plugin that must be installed for pinia to work. Note **you don't need +this plugin if you are using Nuxt.js**. Use the `buildModule` instead: +https://pinia.vuejs.org/ssr/nuxt.html. + +**`Example`** + +```js +import Vue from 'vue' +import { PiniaVuePlugin, createPinia } from 'pinia' + +Vue.use(PiniaVuePlugin) +const pinia = createPinia() + +new Vue({ + el: '#app', + // ... + pinia, +}) +``` + +**`Param`** + +`Vue` imported from 'vue'. + +## Functions + +### acceptHMRUpdate + +▸ **acceptHMRUpdate**(`initialUseStore`, `hot`): (`newModule`: `any`) => `any` + +Creates an _accept_ function to pass to `import.meta.hot` in Vite applications. + +**`Example`** + +```js +const useUser = defineStore(...) +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useUser, import.meta.hot)) +} +``` + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `initialUseStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`string`, [`StateTree`](pinia.md#statetree), [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, [`_ActionsTree`](pinia.md#_actionstree)\> | return of the defineStore to hot update | +| `hot` | `any` | `import.meta.hot` | + +#### Returns + +`fn` + +▸ (`newModule`): `any` + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `newModule` | `any` | + +##### Returns + +`any` + +___ + +### createPinia + +▸ **createPinia**(): [`Pinia`](../interfaces/pinia.Pinia.md) + +Creates a Pinia instance to be used by the application + +#### Returns + +[`Pinia`](../interfaces/pinia.Pinia.md) + +___ + +### defineStore + +▸ **defineStore**<`Id`, `S`, `G`, `A`\>(`id`, `options`): [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> + +Creates a `useStore` function that retrieves the store instance + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) = {} | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> = {} | +| `A` | {} | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `id` | `Id` | id of the store (must be unique) | +| `options` | `Omit`<[`DefineStoreOptions`](../interfaces/pinia.DefineStoreOptions.md)<`Id`, `S`, `G`, `A`\>, ``"id"``\> | options to define the store | + +#### Returns + +[`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> + +▸ **defineStore**<`Id`, `S`, `G`, `A`\>(`options`): [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> + +Creates a `useStore` function that retrieves the store instance + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) = {} | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> = {} | +| `A` | {} | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | [`DefineStoreOptions`](../interfaces/pinia.DefineStoreOptions.md)<`Id`, `S`, `G`, `A`\> | options to define the store | + +#### Returns + +[`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> + +▸ **defineStore**<`Id`, `SS`\>(`id`, `storeSetup`, `options?`): [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, [`_ExtractStateFromSetupStore`](pinia.md#_extractstatefromsetupstore)<`SS`\>, [`_ExtractGettersFromSetupStore`](pinia.md#_extractgettersfromsetupstore)<`SS`\>, [`_ExtractActionsFromSetupStore`](pinia.md#_extractactionsfromsetupstore)<`SS`\>\> + +Creates a `useStore` function that retrieves the store instance + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `SS` | `SS` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `id` | `Id` | id of the store (must be unique) | +| `storeSetup` | () => `SS` | function that defines the store | +| `options?` | [`DefineSetupStoreOptions`](../interfaces/pinia.DefineSetupStoreOptions.md)<`Id`, [`_ExtractStateFromSetupStore`](pinia.md#_extractstatefromsetupstore)<`SS`\>, [`_ExtractGettersFromSetupStore`](pinia.md#_extractgettersfromsetupstore)<`SS`\>, [`_ExtractActionsFromSetupStore`](pinia.md#_extractactionsfromsetupstore)<`SS`\>\> | extra options | + +#### Returns + +[`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, [`_ExtractStateFromSetupStore`](pinia.md#_extractstatefromsetupstore)<`SS`\>, [`_ExtractGettersFromSetupStore`](pinia.md#_extractgettersfromsetupstore)<`SS`\>, [`_ExtractActionsFromSetupStore`](pinia.md#_extractactionsfromsetupstore)<`SS`\>\> + +___ + +### getActivePinia + +▸ **getActivePinia**(): `undefined` \| [`Pinia`](../interfaces/pinia.Pinia.md) + +Get the currently active pinia if there is any. + +#### Returns + +`undefined` \| [`Pinia`](../interfaces/pinia.Pinia.md) + +___ + +### mapActions + +▸ **mapActions**<`Id`, `S`, `G`, `A`, `KeyMapper`\>(`useStore`, `keyMapper`): [`_MapActionsObjectReturn`](pinia.md#_mapactionsobjectreturn)<`A`, `KeyMapper`\> + +Allows directly using actions from your store without using the composition +API (`setup()`) by generating an object to be spread in the `methods` field +of a component. The values of the object are the actions while the keys are +the names of the resulting methods. + +**`Example`** + +```js +export default { + methods: { + // other methods properties + // useCounterStore has two actions named `increment` and `setCount` + ...mapActions(useCounterStore, { moar: 'increment', setIt: 'setCount' }) + }, + + created() { + this.moar() + this.setIt(2) + } +} +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `KeyMapper` | extends `Record`<`string`, keyof `A`\> | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keyMapper` | `KeyMapper` | object to define new names for the actions | + +#### Returns + +[`_MapActionsObjectReturn`](pinia.md#_mapactionsobjectreturn)<`A`, `KeyMapper`\> + +▸ **mapActions**<`Id`, `S`, `G`, `A`\>(`useStore`, `keys`): [`_MapActionsReturn`](pinia.md#_mapactionsreturn)<`A`\> + +Allows directly using actions from your store without using the composition +API (`setup()`) by generating an object to be spread in the `methods` field +of a component. + +**`Example`** + +```js +export default { + methods: { + // other methods properties + ...mapActions(useCounterStore, ['increment', 'setCount']) + }, + + created() { + this.increment() + this.setCount(2) // pass arguments as usual + } +} +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keys` | keyof `A`[] | array of action names to map | + +#### Returns + +[`_MapActionsReturn`](pinia.md#_mapactionsreturn)<`A`\> + +___ + +### mapGetters + +▸ **mapGetters**<`Id`, `S`, `G`, `A`, `KeyMapper`\>(`useStore`, `keyMapper`): [`_MapStateObjectReturn`](pinia.md#_mapstateobjectreturn)<`Id`, `S`, `G`, `A`, `KeyMapper`\> + +Alias for `mapState()`. You should use `mapState()` instead. + +**`Deprecated`** + +use `mapState()` instead. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `KeyMapper` | extends `Record`<`string`, keyof `S` \| keyof `G` \| (`store`: [`Store`](pinia.md#store)<`Id`, `S`, `G`, `A`\>) => `any`\> | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | +| `keyMapper` | `KeyMapper` | + +#### Returns + +[`_MapStateObjectReturn`](pinia.md#_mapstateobjectreturn)<`Id`, `S`, `G`, `A`, `KeyMapper`\> + +▸ **mapGetters**<`Id`, `S`, `G`, `A`, `Keys`\>(`useStore`, `keys`): [`_MapStateReturn`](pinia.md#_mapstatereturn)<`S`, `G`, `Keys`\> + +Alias for `mapState()`. You should use `mapState()` instead. + +**`Deprecated`** + +use `mapState()` instead. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `Keys` | extends `string` \| `number` \| `symbol` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | +| `keys` | readonly `Keys`[] | + +#### Returns + +[`_MapStateReturn`](pinia.md#_mapstatereturn)<`S`, `G`, `Keys`\> + +___ + +### mapState + +▸ **mapState**<`Id`, `S`, `G`, `A`, `KeyMapper`\>(`useStore`, `keyMapper`): [`_MapStateObjectReturn`](pinia.md#_mapstateobjectreturn)<`Id`, `S`, `G`, `A`, `KeyMapper`\> + +Allows using state and getters from one store without using the composition +API (`setup()`) by generating an object to be spread in the `computed` field +of a component. The values of the object are the state properties/getters +while the keys are the names of the resulting computed properties. +Optionally, you can also pass a custom function that will receive the store +as its first argument. Note that while it has access to the component +instance via `this`, it won't be typed. + +**`Example`** + +```js +export default { + computed: { + // other computed properties + // useCounterStore has a state property named `count` and a getter `double` + ...mapState(useCounterStore, { + n: 'count', + triple: store => store.n * 3, + // note we can't use an arrow function if we want to use `this` + custom(store) { + return this.someComponentValue + store.n + }, + doubleN: 'double' + }) + }, + + created() { + this.n // 2 + this.doubleN // 4 + } +} +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `KeyMapper` | extends `Record`<`string`, keyof `S` \| keyof `G` \| (`store`: [`Store`](pinia.md#store)<`Id`, `S`, `G`, `A`\>) => `any`\> | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keyMapper` | `KeyMapper` | object of state properties or getters | + +#### Returns + +[`_MapStateObjectReturn`](pinia.md#_mapstateobjectreturn)<`Id`, `S`, `G`, `A`, `KeyMapper`\> + +▸ **mapState**<`Id`, `S`, `G`, `A`, `Keys`\>(`useStore`, `keys`): [`_MapStateReturn`](pinia.md#_mapstatereturn)<`S`, `G`, `Keys`\> + +Allows using state and getters from one store without using the composition +API (`setup()`) by generating an object to be spread in the `computed` field +of a component. + +**`Example`** + +```js +export default { + computed: { + // other computed properties + ...mapState(useCounterStore, ['count', 'double']) + }, + + created() { + this.count // 2 + this.double // 4 + } +} +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `Keys` | extends `string` \| `number` \| `symbol` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keys` | readonly `Keys`[] | array of state properties or getters | + +#### Returns + +[`_MapStateReturn`](pinia.md#_mapstatereturn)<`S`, `G`, `Keys`\> + +___ + +### mapStores + +▸ **mapStores**<`Stores`\>(...`stores`): [`_Spread`](pinia.md#_spread)<`Stores`\> + +Allows using stores without the composition API (`setup()`) by generating an +object to be spread in the `computed` field of a component. It accepts a list +of store definitions. + +**`Example`** + +```js +export default { + computed: { + // other computed properties + ...mapStores(useUserStore, useCartStore) + }, + + created() { + this.userStore // store with id "user" + this.cartStore // store with id "cart" + } +} +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Stores` | extends `any`[] | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `...stores` | [...Stores[]] | list of stores to map to an object | + +#### Returns + +[`_Spread`](pinia.md#_spread)<`Stores`\> + +___ + +### mapWritableState + +▸ **mapWritableState**<`Id`, `S`, `G`, `A`, `KeyMapper`\>(`useStore`, `keyMapper`): [`_MapWritableStateObjectReturn`](pinia.md#_mapwritablestateobjectreturn)<`S`, `KeyMapper`\> + +Same as `mapState()` but creates computed setters as well so the state can be +modified. Differently from `mapState()`, only `state` properties can be +added. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | +| `KeyMapper` | extends `Record`<`string`, keyof `S`\> | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keyMapper` | `KeyMapper` | object of state properties | + +#### Returns + +[`_MapWritableStateObjectReturn`](pinia.md#_mapwritablestateobjectreturn)<`S`, `KeyMapper`\> + +▸ **mapWritableState**<`Id`, `S`, `G`, `A`\>(`useStore`, `keys`): [`_MapWritableStateReturn`](pinia.md#_mapwritablestatereturn)<`S`\> + +Allows using state and getters from one store without using the composition +API (`setup()`) by generating an object to be spread in the `computed` field +of a component. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Id` | extends `string` | +| `S` | extends [`StateTree`](pinia.md#statetree) | +| `G` | extends [`_GettersTree`](pinia.md#_getterstree)<`S`\> | +| `A` | `A` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `useStore` | [`StoreDefinition`](../interfaces/pinia.StoreDefinition.md)<`Id`, `S`, `G`, `A`\> | store to map from | +| `keys` | keyof `S`[] | array of state properties | + +#### Returns + +[`_MapWritableStateReturn`](pinia.md#_mapwritablestatereturn)<`S`\> + +___ + +### setActivePinia + +▸ **setActivePinia**(`pinia`): `undefined` \| [`Pinia`](../interfaces/pinia.Pinia.md) + +Sets or unsets the active pinia. Used in SSR and internally when calling +actions and getters + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `pinia` | `undefined` \| [`Pinia`](../interfaces/pinia.Pinia.md) | Pinia instance | + +#### Returns + +`undefined` \| [`Pinia`](../interfaces/pinia.Pinia.md) + +___ + +### setMapStoreSuffix + +▸ **setMapStoreSuffix**(`suffix`): `void` + +Changes the suffix added by `mapStores()`. Can be set to an empty string. +Defaults to `"Store"`. Make sure to extend the MapStoresCustomization +interface if you are using TypeScript. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `suffix` | `string` | new suffix | + +#### Returns + +`void` + +___ + +### skipHydrate + +▸ **skipHydrate**<`T`\>(`obj`): `T` + +Tells Pinia to skip the hydration process of a given object. This is useful in setup stores (only) when you return a +stateful object in the store but it isn't really state. e.g. returning a router instance in a setup store. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | `any` | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `obj` | `T` | target object | + +#### Returns + +`T` + +obj + +___ + +### storeToRefs + +▸ **storeToRefs**<`SS`\>(`store`): `ToRefs`<[`StoreState`](pinia.md#storestate)<`SS`\> & [`StoreGetters`](pinia.md#storegetters)<`SS`\> & [`PiniaCustomStateProperties`](../interfaces/pinia.PiniaCustomStateProperties.md)<[`StoreState`](pinia.md#storestate)<`SS`\>\>\> + +Creates an object of references with all the state, getters, and plugin-added +state properties of the store. Similar to `toRefs()` but specifically +designed for Pinia stores so methods and non reactive properties are +completely ignored. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `SS` | extends [`_StoreWithState`](../interfaces/pinia._StoreWithState.md)<`string`, [`StateTree`](pinia.md#statetree), [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, [`_ActionsTree`](pinia.md#_actionstree), `SS`\> & [`StateTree`](pinia.md#statetree) & [`_StoreWithGetters`](pinia.md#_storewithgetters)<[`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>\> & [`PiniaCustomProperties`](../interfaces/pinia.PiniaCustomProperties.md)<`string`, [`StateTree`](pinia.md#statetree), [`_GettersTree`](pinia.md#_getterstree)<[`StateTree`](pinia.md#statetree)\>, [`_ActionsTree`](pinia.md#_actionstree), `SS`\> & [`PiniaCustomStateProperties`](../interfaces/pinia.PiniaCustomStateProperties.md)<[`StateTree`](pinia.md#statetree), `SS`\> | + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `store` | `SS` | store to extract the refs from | + +#### Returns + +`ToRefs`<[`StoreState`](pinia.md#storestate)<`SS`\> & [`StoreGetters`](pinia.md#storegetters)<`SS`\> & [`PiniaCustomStateProperties`](../interfaces/pinia.PiniaCustomStateProperties.md)<[`StoreState`](pinia.md#storestate)<`SS`\>\>\> diff --git a/packages/docs-new/api/modules/pinia_nuxt.md b/packages/docs-new/api/modules/pinia_nuxt.md new file mode 100644 index 00000000..63178af0 --- /dev/null +++ b/packages/docs-new/api/modules/pinia_nuxt.md @@ -0,0 +1,31 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / @pinia/nuxt + +# Module: @pinia/nuxt + +## Interfaces + +- [ModuleOptions](../interfaces/pinia_nuxt.ModuleOptions.md) + +## Functions + +### default + +▸ **default**(`this`, `inlineOptions`, `nuxt`): `void` \| `Promise`<`void`\> + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `this` | `void` | +| `inlineOptions` | [`ModuleOptions`](../interfaces/pinia_nuxt.ModuleOptions.md) | +| `nuxt` | `Nuxt` | + +#### Returns + +`void` \| `Promise`<`void`\> diff --git a/packages/docs-new/api/modules/pinia_testing.md b/packages/docs-new/api/modules/pinia_testing.md new file mode 100644 index 00000000..d15b7146 --- /dev/null +++ b/packages/docs-new/api/modules/pinia_testing.md @@ -0,0 +1,39 @@ +--- +sidebar: "auto" +editLinks: false +sidebarDepth: 3 +--- + +[API Documentation](../index.md) / @pinia/testing + +# Module: @pinia/testing + +## Interfaces + +- [TestingOptions](../interfaces/pinia_testing.TestingOptions.md) +- [TestingPinia](../interfaces/pinia_testing.TestingPinia.md) + +## Functions + +### createTestingPinia + +▸ **createTestingPinia**(`options?`): [`TestingPinia`](../interfaces/pinia_testing.TestingPinia.md) + +Creates a pinia instance designed for unit tests that **requires mocking** +the stores. By default, **all actions are mocked** and therefore not +executed. This allows you to unit test your store and components separately. +You can change this with the `stubActions` option. If you are using jest, +they are replaced with `jest.fn()`, otherwise, you must provide your own +`createSpy` option. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | [`TestingOptions`](../interfaces/pinia_testing.TestingOptions.md) | options to configure the testing pinia | + +#### Returns + +[`TestingPinia`](../interfaces/pinia_testing.TestingPinia.md) + +a augmented pinia instance diff --git a/packages/docs-new/cookbook/composables.md b/packages/docs-new/cookbook/composables.md new file mode 100644 index 00000000..864f790c --- /dev/null +++ b/packages/docs-new/cookbook/composables.md @@ -0,0 +1,99 @@ +# Dealing with Composables + +[Composables](https://vuejs.org/guide/reusability/composables.html#composables) are functions that leverage Vue Composition API to encapsulate and reuse stateful logic. Whether you write your own, you use [external libraries](https://vueuse.org/) or do both, you can fully use the power of Composables in your pinia stores. + +## Option Stores + +When defining an option store, you can call a composable inside of the `state` property: + +```ts +export const useAuthStore = defineStore('auth', { + state: () => ({ + user: useLocalStorage('pinia/auth/login', 'bob'), + }), +}) +``` + +Keep in mind that **you can only return writable state** (e.g. a `ref()`). Here are some examples of composables that you can use: + +- [useLocalStorage](https://vueuse.org/core/useLocalStorage/) +- [useAsyncState](https://vueuse.org/core/useAsyncState/) + +Here are some examples of composables that cannot be used in an option stores (but can be used with setup stores): + +- [useMediaControls](https://vueuse.org/core/useMediaControls/): exposes functions +- [useMemoryInfo](https://vueuse.org/core/useMemory/): exposes readonly data +- [useEyeDropper](https://vueuse.org/core/useEyeDropper/): exposes readonly data and functions + +## Setup Stores + +On the other hand, when defining a setup store, you can use almost any composable since every property gets discerned into state, action, or getter: + +```ts +import { defineStore, skipHydrate } from 'pinia' +import { useMediaControls } from '@vueuse/core' + +export const useVideoPlayer = defineStore('video', () => { + // we won't expose this element directly + const videoElement = ref() + const src = ref('/data/video.mp4') + const { playing, volume, currentTime, togglePictureInPicture } = + useMediaControls(video, { src }) + + function loadVideo(element: HTMLVideoElement, src: string) { + videoElement.value = element + src.value = src + } + + return { + src, + playing, + volume, + currentTime, + + loadVideo, + togglePictureInPicture, + } +}) +``` + +## SSR + +When dealing with [Server Side Rendering](../ssr/index.md), you need to take care of some extra steps in order to use composables within your stores. + +In [Option Stores](#option-stores), you need to define a `hydrate()` function. This function is called when the store is instantiated on the client (the browser) when there is an initial state available at the time the store is created. The reason we need to define this function is because in such scenario, `state()` is not called. + +```ts +import { defineStore, skipHydrate } from 'pinia' +import { useLocalStorage } from '@vueuse/core' + +export const useAuthStore = defineStore('auth', { + state: () => ({ + user: useLocalStorage('pinia/auth/login', 'bob'), + }), + + hydrate(state, initialState) { + // in this case we can completely ignore the initial state since we + // want to read the value from the browser + state.user = useLocalStorage('pinia/auth/login', 'bob') + }, +}) +``` + +In [Setup Stores](#setup-stores), you need to use a helper named `skipHydrate()` on any state property that shouldn't be picked up from the initial state. Differently from option stores, setup stores cannot just _skip calling `state()`_, so we mark properties that cannot be hydrated with `skipHydrate()`. Note that this only applies to writable reactive properties: + +```ts +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) + // ... + return { + lastColor: skipHydrate(lastColor), // Ref + open, // Function + isSupported, // boolean (not even reactive) + } +}) +``` diff --git a/packages/docs-new/cookbook/composing-stores.md b/packages/docs-new/cookbook/composing-stores.md new file mode 100644 index 00000000..2100c5cc --- /dev/null +++ b/packages/docs-new/cookbook/composing-stores.md @@ -0,0 +1,109 @@ +# Composing Stores + +Composing stores is about having stores that use each other, and this is supported in Pinia. There is one rule to follow: + +If **two or more stores use each other**, they cannot create an infinite loop through _getters_ or _actions_. They cannot **both** directly read each other state in their setup function: + +```js +const useX = defineStore('x', () => { + const y = useY() + + // ❌ This is not possible because y also tries to read x.name + y.name + + function doSomething() { + // ✅ Read y properties in computed or actions + const yName = y.name + // ... + } + + return { + name: ref('I am X'), + } +}) + +const useY = defineStore('y', () => { + const x = useX() + + // ❌ This is not possible because x also tries to read y.name + x.name + + function doSomething() { + // ✅ Read x properties in computed or actions + const xName = x.name + // ... + } + + return { + name: ref('I am Y'), + } +}) +``` + +## Nested Stores + +Note that if one store uses another store, you can directly import and call the `useStore()` function within _actions_ and _getters_. Then you can interact with the store just like you would from within a Vue component. See [Shared Getters](#shared-getters) and [Shared Actions](#shared-actions). + +When it comes to _setup stores_, you can simply use one of the stores **at the top** of the store function: + +```ts +import { useUserStore } from './user' + +export const useCartStore = defineStore('cart', () => { + const user = useUserStore() + + const summary = computed(() => { + return `Hi ${user.name}, you have ${state.list.length} items in your cart. It costs ${state.price}.` + }) + + function purchase() { + return apiPurchase(user.id, this.list) + } + + return { summary, purchase } +}) +``` + +## Shared Getters + +You can simply call `useOtherStore()` inside a _getter_: + +```js +import { defineStore } from 'pinia' +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 in your cart. It costs ${state.price}.` + }, + }, +}) +``` + +## Shared Actions + +The same applies to _actions_: + +```js +import { defineStore } from 'pinia' +import { useUserStore } from './user' + +export const useCartStore = defineStore('cart', { + actions: { + async orderCart() { + const user = useUserStore() + + try { + await apiOrderCart(user.token, this.items) + // another action + this.emptyCart() + } catch (err) { + displayError(err) + } + }, + }, +}) +``` diff --git a/packages/docs-new/cookbook/hot-module-replacement.md b/packages/docs-new/cookbook/hot-module-replacement.md new file mode 100644 index 00000000..0f32c675 --- /dev/null +++ b/packages/docs-new/cookbook/hot-module-replacement.md @@ -0,0 +1,20 @@ +# HMR (Hot Module Replacement) + +Pinia supports Hot Module replacement so you can edit your stores and interact with them directly in your app without reloading the page, allowing you to keep the existing state, add, or even remove state, actions, and getters. + +At the moment, only [Vite](https://vitejs.dev/) is officially supported but any bundler implementing the `import.meta.hot` spec should work (e.g. [webpack](https://webpack.js.org/api/module-variables/#importmetawebpackhot) seems to use `import.meta.webpackHot` instead of `import.meta.hot`). +You need to add this snippet of code next to any store declaration. Let's say you have three stores: `auth.js`, `cart.js`, and `chat.js`, you will have to add (and adapt) this after the creation of the _store definition_: + +```js +// auth.js +import { defineStore, acceptHMRUpdate } from 'pinia' + +const useAuth = defineStore('auth', { + // options... +}) + +// make sure to pass the right store definition, `useAuth` in this case. +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot)) +} +``` diff --git a/packages/docs-new/cookbook/index.md b/packages/docs-new/cookbook/index.md new file mode 100644 index 00000000..0bdf9a5e --- /dev/null +++ b/packages/docs-new/cookbook/index.md @@ -0,0 +1,8 @@ +# Cookbook + +- [Migrating from Vuex ≤4](./migration-vuex.md): A migration guide for converting Vuex ≤4 projects. +- [HMR](./hot-module-replacement.md): How to activate hot module replacement and improve the developer experience. +- [Testing Stores (WIP)](./testing.md): How to unit test Stores and mock them in component unit tests. +- [Composing Stores](./composing-stores.md): How to cross use multiple stores. e.g. using the user store in the cart store. +- [Options API](./options-api.md): How to use Pinia without the composition API, outside of `setup()`. +- [Migrating from 0.0.7](./migration-0-0-7.md): A migration guide with more examples than the changelog. diff --git a/packages/docs-new/cookbook/migration-0-0-7.md b/packages/docs-new/cookbook/migration-0-0-7.md new file mode 100644 index 00000000..62a75bf5 --- /dev/null +++ b/packages/docs-new/cookbook/migration-0-0-7.md @@ -0,0 +1,122 @@ +# Migrating from 0.0.7 + +The versions after `0.0.7`: `0.1.0`, and `0.2.0`, came with a few big breaking changes. This guide helps you migrate whether you use Vue 2 or Vue 3. The whole changelog can be found in the repository: + +- [For Pinia <= 1 for Vue 2](https://github.com/vuejs/pinia/blob/v1/CHANGELOG.md) +- [For Pinia >= 2 for Vue 3](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md) + +If you have questions or issues regarding the migration, feel free to [open a discussion](https://github.com/vuejs/pinia/discussions/categories/q-a) to ask for help. + +## No more `store.state` + +You no longer access the store state via a `state` property, you can directly access any state property. + +Given a store defined with: + +```js +const useStore({ + id: 'main', + state: () => ({ count: 0 }) +}) +``` + +Do + +```diff + const store = useStore() + +-store.state.count++ ++store.count.++ +``` + +You can still access the whole store state with `$state` when needed: + +```diff +-store.state = newState ++store.$state = newState +``` + +## Rename of store properties + +All store properties (`id`, `patch`, `reset`, etc) are now prefixed with `$` to allow properties defined on the store with the same names. Tip: you can refactor your whole codebase with F2 (or right-click + Refactor) on each of the store's properties + +```diff + const store = useStore() +-store.patch({ count: 0 }) ++store.$patch({ count: 0 }) + +-store.reset() ++store.$reset() + +-store.id ++store.$id +``` + +## The Pinia instance + +It's now necessary to create a pinia instance and install it: + +If you are using Vue 2 (Pinia <= 1): + +```js +import Vue from 'vue' +import { createPinia, PiniaVuePlugin } from 'pinia' + +const pinia = createPinia() +Vue.use(PiniaVuePlugin) +new Vue({ + el: '#app', + pinia, + // ... +}) +``` + +If you are using Vue 3 (Pinia >= 2): + +```js +import { createApp } from 'vue' +import { createPinia, PiniaVuePlugin } from 'pinia' +import App from './App.vue' + +const pinia = createPinia() +createApp(App).use(pinia).mount('#app') +``` + +The `pinia` instance is what holds the state and should **be unique per application**. Check the SSR section of the docs for more details. + +## SSR changes + +The SSR plugin `PiniaSsr` is no longer necessary and has been removed. +With the introduction of pinia instances, `getRootState()` is no longer necessary and should be replaced with `pinia.state.value`: + +If you are using Vue 2 (Pinia <= 1): + +```diff +// entry-server.js +-import { getRootState, PiniaSsr } from 'pinia', ++import { createPinia, PiniaVuePlugin } from 'pinia', + + +-// install plugin to automatically use correct context in setup and onServerPrefetch +-Vue.use(PiniaSsr); ++Vue.use(PiniaVuePlugin) + + export default context => { ++ const pinia = createPinia() + const app = new Vue({ + // other options ++ pinia + }) + + context.rendered = () => { + // pass state to context +- context.piniaState = getRootState(context.req) ++ context.piniaState = pinia.state.value + }; + +- return { app } ++ return { app, pinia } + } +``` + +`setActiveReq()` and `getActiveReq()` have been replaced with `setActivePinia()` and `getActivePinia()` respectively. `setActivePinia()` can only be passed a `pinia` instance created with `createPinia()`. **Note that most of the time you won't directly use these functions**. diff --git a/packages/docs-new/cookbook/migration-v1-v2.md b/packages/docs-new/cookbook/migration-v1-v2.md new file mode 100644 index 00000000..72885d3d --- /dev/null +++ b/packages/docs-new/cookbook/migration-v1-v2.md @@ -0,0 +1,178 @@ +# Migrating from 0.x (v1) to v2 + +Starting at version `2.0.0-rc.4`, pinia supports both Vue 2 and Vue 3! This means, all new updates will be applied to this version 2 so both Vue 2 and Vue 3 users can benefit from it. If you are using Vue 3, this doesn't change anything for you as you were already using the rc and you can check [the CHANGELOG](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md) for a detailed explanation of everything that changed. Otherwise, **this guide is for you**! + +## Deprecations + +Let's take a look at all the changes you need to apply to your code. First, make sure you are already running the latest 0.x version to see any deprecations: + +```shell +npm i 'pinia@^0.x.x' +# or with yarn +yarn add 'pinia@^0.x.x' +``` + +If you are using ESLint, consider using [this plugin](https://github.com/gund/eslint-plugin-deprecation) to find all deprecated usages. Otherwise, you should be able to see them as they appear crossed. These are the APIs that were deprecated that were removed: + +- `createStore()` becomes `defineStore()` +- In subscriptions, `storeName` becomes `storeId` +- `PiniaPlugin` was renamed `PiniaVuePlugin` (Pinia plugin for Vue 2) +- `$subscribe()` no longer accepts a _boolean_ as second parameter, pass an object with `detached: true` instead. +- Pinia plugins no longer directly receive the `id` of the store. Use `store.$id` instead. + +## Breaking changes + +After removing these, you can upgrade to v2 with: + +```shell +npm i 'pinia@^2.x.x' +# or with yarn +yarn add 'pinia@^2.x.x' +``` + +And start updating your code. + +### Generic Store type + +Added in [2.0.0-rc.0](https://github.com/vuejs/pinia/blob/v2/packages/pinia/CHANGELOG.md#200-rc0-2021-07-28) + +Replace any usage of the type `GenericStore` with `StoreGeneric`. This is the new generic store type that should accept any kind of store. If you were writing functions using the type `Store` without passing its generics (e.g. `Store`), you should also use `StoreGeneric` as the `Store` type without generics creates an empty store type. + +```diff +-function takeAnyStore(store: Store) {} ++function takeAnyStore(store: StoreGeneric) {} + +-function takeAnyStore(store: GenericStore) {} ++function takeAnyStore(store: StoreGeneric) {} +``` + +## `DefineStoreOptions` for plugins + +If you were writing plugins, using TypeScript, and extending the type `DefineStoreOptions` to add custom options, you should rename it to `DefineStoreOptionsBase`. This type will apply to both setup and options stores. + +```diff + declare module 'pinia' { +- export interface DefineStoreOptions { ++ export interface DefineStoreOptionsBase { + debounce?: { + [k in keyof StoreActions]?: number + } + } + } +``` + +## `PiniaStorePlugin` was renamed + +The type `PiniaStorePlugin` was renamed to `PiniaPlugin`. + +```diff +-import { PiniaStorePlugin } from 'pinia' ++import { PiniaPlugin } from 'pinia' + +-const piniaPlugin: PiniaStorePlugin = () => { ++const piniaPlugin: PiniaPlugin = () => { + // ... + } +``` + +**Note this change can only be done after upgrading to the latest version of Pinia without deprecations**. + +## `@vue/composition-api` version + +Since pinia now relies on `effectScope()`, you must use at least the version `1.1.0` of `@vue/composition-api`: + +```shell +npm i @vue/composition-api@latest +# or with yarn +yarn add @vue/composition-api@latest +``` + +## webpack 4 support + +If you are using webpack 4 (Vue CLI uses webpack 4), you might encounter an error like this: + +``` +ERROR Failed to compile with 18 errors + + error in ./node_modules/pinia/dist/pinia.mjs + +Can't import the named export 'computed' from non EcmaScript module (only default export is available) +``` + +This is due to the modernization of dist files to support native ESM modules in Node.js. Files are now using the extension `.mjs` and `.cjs` to let Node benefit from this. To fix this issue you have two possibilities: + +- If you are using Vue CLI 4.x, upgrade your dependencies. This should include the fix below. + - If upgrading is not possible for you, add this to your `vue.config.js`: + ```js + // vue.config.js + module.exports = { + configureWebpack: { + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + }, + } + ``` +- If you are manually handling webpack, you will have to let it know how to handle `.mjs` files: + ```js + // webpack.config.js + module.exports = { + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, + } + ``` + +## Devtools + +Pinia v2 no longer hijacks Vue Devtools v5, it requires Vue Devtools v6. Find the download link on the [Vue Devtools documentation](https://devtools.vuejs.org/guide/installation.html#chrome) for the **beta channel** of the extension. + +## Nuxt + +If you are using Nuxt, pinia has now it's dedicated Nuxt package 🎉. Install it with: + +```shell +npm i @pinia/nuxt +# or with yarn +yarn add @pinia/nuxt +``` + +Also make sure to **update your `@nuxtjs/composition-api` package**. + +Then adapt your `nuxt.config.js` and your `tsconfig.json` if you are using TypeScript: + +```diff + // nuxt.config.js + module.exports { + buildModules: [ + '@nuxtjs/composition-api/module', +- 'pinia/nuxt', ++ '@pinia/nuxt', + ], + } +``` + +```diff + // tsconfig.json + { + "types": [ + // ... +- "pinia/nuxt/types" ++ "@pinia/nuxt" + ] + } +``` + +It is also recommended to give [the dedicated Nuxt section](../ssr/nuxt.md) a read. diff --git a/packages/docs-new/cookbook/migration-vuex.md b/packages/docs-new/cookbook/migration-vuex.md new file mode 100644 index 00000000..31bb9eab --- /dev/null +++ b/packages/docs-new/cookbook/migration-vuex.md @@ -0,0 +1,287 @@ +# Migrating from Vuex ≤4 + +Although the structure of Vuex and Pinia stores is different, a lot of the logic can be reused. This guide serves to help you through the process and point out some common gotchas that can appear. + +## Preparation + +First, follow the [Getting Started guide](../getting-started.md) to install Pinia. + +## Restructuring Modules to Stores + +Vuex has the concept of a single store with multiple _modules_. These modules can optionally be namespaced and even nested within each other. + +The easiest way to transition that concept to be used with Pinia is that each module you used previously is now a _store_. Each store requires an `id` which is similar to a namespace in Vuex. This means that each store is namespaced by design. Nested modules can also each become their own store. Stores that depend on each other will simply import the other store. + +How you choose to restructure your Vuex modules into Pinia stores is entirely up to you, but here is one suggestion: + +```bash +# Vuex example (assuming namespaced modules) +src +└── store + ├── index.js # Initializes Vuex, imports modules + └── modules + ├── module1.js # 'module1' namespace + └── nested + ├── index.js # 'nested' namespace, imports module2 & module3 + ├── module2.js # 'nested/module2' namespace + └── module3.js # 'nested/module3' namespace + +# Pinia equivalent, note ids match previous namespaces +src +└── stores + ├── index.js # (Optional) Initializes Pinia, does not import stores + ├── module1.js # 'module1' id + ├── nested-module2.js # 'nestedModule2' id + ├── nested-module3.js # 'nestedModule3' id + └── nested.js # 'nested' id +``` + +This creates a flat structure for stores but also preserves the previous namespacing with equivalent `id`s. If you had some state/getters/actions/mutations in the root of the store (in the `store/index.js` file of Vuex) you may wish to create another store called something like `root` which holds all that information. + +The directory for Pinia is generally called `stores` instead of `store`. This is to emphasize that Pinia uses multiple stores, instead of a single store in Vuex. + +For large projects you may wish to do this conversion module by module rather than converting everything at once. You can actually mix Pinia and Vuex together during the migration so this approach can also work and is another reason for naming the Pinia directory `stores` instead. + +## Converting a Single Module + +Here is a complete example of the before and after of converting a Vuex module to a Pinia store, see below for a step-by-step guide. The Pinia example uses an option store as the structure is most similar to Vuex: + +```ts +// Vuex module in the 'auth/user' namespace +import { Module } from 'vuex' +import { api } from '@/api' +import { RootState } from '@/types' // if using a Vuex type definition + +interface State { + firstName: string + lastName: string + userId: number | null +} + +const storeModule: Module = { + namespaced: true, + state: { + firstName: '', + lastName: '', + userId: null + }, + getters: { + firstName: (state) => state.firstName, + fullName: (state) => `${state.firstName} ${state.lastName}`, + loggedIn: (state) => state.userId !== null, + // combine with some state from other modules + fullUserDetails: (state, getters, rootState, rootGetters) => { + return { + ...state, + fullName: getters.fullName, + // read the state from another module named `auth` + ...rootState.auth.preferences, + // read a getter from a namespaced module called `email` nested under `auth` + ...rootGetters['auth/email'].details + } + } + }, + actions: { + async loadUser ({ state, commit }, id: number) { + if (state.userId !== null) throw new Error('Already logged in') + const res = await api.user.load(id) + commit('updateUser', res) + } + }, + mutations: { + updateUser (state, payload) { + state.firstName = payload.firstName + state.lastName = payload.lastName + state.userId = payload.userId + }, + clearUser (state) { + state.firstName = '' + state.lastName = '' + state.userId = null + } + } +} + +export default storeModule +``` + +```ts +// Pinia Store +import { defineStore } from 'pinia' +import { useAuthPreferencesStore } from './auth-preferences' +import { useAuthEmailStore } from './auth-email' +import vuexStore from '@/store' // for gradual conversion, see fullUserDetails + +interface State { + firstName: string + lastName: string + userId: number | null +} + +export const useAuthUserStore = defineStore('authUser', { + // convert to a function + state: (): State => ({ + firstName: '', + lastName: '', + userId: null + }), + getters: { + // firstName getter removed, no longer needed + fullName: (state) => `${state.firstName} ${state.lastName}`, + loggedIn: (state) => state.userId !== null, + // must define return type because of using `this` + fullUserDetails (state): FullUserDetails { + // import from other stores + const authPreferencesStore = useAuthPreferencesStore() + const authEmailStore = useAuthEmailStore() + return { + ...state, + // other getters now on `this` + fullName: this.fullName, + ...authPreferencesStore.$state, + ...authEmailStore.details + } + + // alternative if other modules are still in Vuex + // return { + // ...state, + // fullName: this.fullName, + // ...vuexStore.state.auth.preferences, + // ...vuexStore.getters['auth/email'].details + // } + } + }, + actions: { + // no context as first argument, use `this` instead + async loadUser (id: number) { + if (this.userId !== null) throw new Error('Already logged in') + const res = await api.user.load(id) + this.updateUser(res) + }, + // mutations can now become actions, instead of `state` as first argument use `this` + updateUser (payload) { + this.firstName = payload.firstName + this.lastName = payload.lastName + this.userId = payload.userId + }, + // easily reset state using `$reset` + clearUser () { + this.$reset() + } + } +}) +``` + +Let's break the above down into steps: + +1. Add a required `id` for the store, you may wish to keep this the same as the namespace before. It is also recommended to make sure the `id` is in _camelCase_ as it makes it easier to use with `mapStores()`. +2. Convert `state` to a function if it was not one already +3. Convert `getters` + 1. Remove any getters that return state under the same name (eg. `firstName: (state) => state.firstName`), these are not necessary as you can access any state directly from the store instance + 2. If you need to access other getters, they are on `this` instead of using the second argument. Remember that if you are using `this` then you will have to use a regular function instead of an arrow function. Also note that you will need to specify a return type because of TS limitations, see [here](../core-concepts/getters.md#accessing-other-getters) for more details + 3. If using `rootState` or `rootGetters` arguments, replace them by importing the other store directly, or if they still exist in Vuex then access them directly from Vuex +4. Convert `actions` + 1. Remove the first `context` argument from each action. Everything should be accessible from `this` instead + 2. If using other stores either import them directly or access them on Vuex, the same as for getters +5. Convert `mutations` + 1. Mutations do not exist any more. These can be converted to `actions` instead, or you can just assign directly to the store within your components (eg. `userStore.firstName = 'First'`) + 2. If converting to actions, remove the first `state` argument and replace any assignments with `this` instead + 3. A common mutation is to reset the state back to its initial state. This is built in functionality with the store's `$reset` method. Note that this functionality only exists for option stores. + +As you can see most of your code can be reused. Type safety should also help you identify what needs to be changed if anything is missed. + +## Usage Inside Components + +Now that your Vuex module has been converted to a Pinia store, any component or other file that uses that module needs to be updated too. + +If you were using `map` helpers from Vuex before, it's worth looking at the [Usage without setup() guide](./options-api.md) as most of those helpers can be reused. + +If you were using `useStore` then instead import the new store directly and access the state on it. For example: + +```ts +// Vuex +import { defineComponent, computed } from 'vue' +import { useStore } from 'vuex' + +export default defineComponent({ + setup () { + const store = useStore() + + const firstName = computed(() => store.state.auth.user.firstName) + const fullName = computed(() => store.getters['auth/user/fullName']) + + return { + firstName, + fullName + } + } +}) +``` + +```ts +// Pinia +import { defineComponent, computed } from 'vue' +import { useAuthUserStore } from '@/stores/auth-user' + +export default defineComponent({ + setup () { + const authUserStore = useAuthUserStore() + + const firstName = computed(() => authUserStore.firstName) + const fullName = computed(() => authUserStore.fullName) + + return { + // you can also access the whole store in your component by returning it + authUserStore, + firstName, + fullName + } + } +}) +``` + +## Usage Outside Components + +Updating usage outside of components should be simple as long as you're careful to _not use a store outside of functions_. Here is an example of using the store in a Vue Router navigation guard: + +```ts +// Vuex +import vuexStore from '@/store' + +router.beforeEach((to, from, next) => { + if (vuexStore.getters['auth/user/loggedIn']) next() + else next('/login') +}) +``` + +```ts +// Pinia +import { useAuthUserStore } from '@/stores/auth-user' + +router.beforeEach((to, from, next) => { + // Must be used within the function! + const authUserStore = useAuthUserStore() + if (authUserStore.loggedIn) next() + else next('/login') +}) +``` + +More details can be found [here](../core-concepts/outside-component-usage.md). + +## Advanced Vuex Usage + +In the case your Vuex store using some of the more advanced features it offers, here is some guidance on how to accomplish the same in Pinia. Some of these points are already covered in [this comparison summary](../introduction.md#comparison-with-vuex-3-x-4-x). + +### Dynamic Modules + +There is no need to dynamically register modules in Pinia. Stores are dynamic by design and are only registered when they are needed. If a store is never used, it will never be "registered". + +### Hot Module Replacement + +HMR is also supported but will need to be replaced, see the [HMR Guide](./hot-module-replacement.md). + +### Plugins + +If you use a public Vuex plugin then check if there is a Pinia alternative. If not you will need to write your own or evaluate whether the plugin is still necessary. + +If you have written a plugin of your own, then it can likely be updated to work with Pinia. See the [Plugin Guide](../core-concepts/plugins.md). diff --git a/packages/docs-new/cookbook/options-api.md b/packages/docs-new/cookbook/options-api.md new file mode 100644 index 00000000..6c352f72 --- /dev/null +++ b/packages/docs-new/cookbook/options-api.md @@ -0,0 +1,77 @@ +# Usage without `setup()` + +Pinia can be used even if you are not using the composition API (if you are using Vue <2.7, you still need to install the `@vue/composition-api` plugin though). While we recommend you give the Composition API a try and learn it, it might not be the time for you and your team yet, you might be in the process of migrating an application, or any other reason. There are a few functions: + +- [mapStores](#giving-access-to-the-whole-store) +- [mapState](../core-concepts/state.md#usage-with-the-options-api) +- [mapWritableState](../core-concepts/state.md#modifiable-state) +- ⚠️ [mapGetters](../core-concepts/getters.md#without-setup) (just for migration convenience, use `mapState()` instead) +- [mapActions](../core-concepts/actions.md#without-setup) + +## Giving access to the whole store + +If you need to access pretty much everything from the store, it might be too much to map every single property of the store... Instead you can get access to the whole store with `mapStores()`: + +```js +import { mapStores } from 'pinia' + +// given two stores with the following ids +const useUserStore = defineStore('user', { + // ... +}) +const useCartStore = defineStore('cart', { + // ... +}) + +export default { + computed: { + // note we are not passing an array, just one store after the other + // each store will be accessible as its id + 'Store' + ...mapStores(useCartStore, useUserStore) + }, + + methods: { + async buyStuff() { + // use them anywhere! + if (this.userStore.isAuthenticated()) { + await this.cartStore.buy() + this.$router.push('/purchased') + } + }, + }, +} +``` + +By default, Pinia will add the `"Store"` suffix to the `id` of each store. You can customize this behavior by calling the `setMapStoreSuffix()`: + +```js +import { createPinia, setMapStoreSuffix } from 'pinia' + +// completely remove the suffix: this.user, this.cart +setMapStoreSuffix('') +// this.user_store, this.cart_store (it's okay, I won't judge you) +setMapStoreSuffix('_store') +export const pinia = createPinia() +``` + +## TypeScript + +By default, all map helpers support autocompletion and you don't need to do anything. If you call `setMapStoreSuffix()` to change the `"Store"` suffix, you will need to also add it somewhere in a TS file or your `global.d.ts` file. The most convenient place would be the same place where you call `setMapStoreSuffix()`: + +```ts +import { createPinia, setMapStoreSuffix } from 'pinia' + +setMapStoreSuffix('') // completely remove the suffix +export const pinia = createPinia() + +declare module 'pinia' { + export interface MapStoresCustomization { + // set it to the same value as above + suffix: '' + } +} +``` + +:::warning +If you are using a TypeScript declaration file (like `global.d.ts`), make sure to `import 'pinia'` at the top of it to expose all existing types. +::: diff --git a/packages/docs-new/cookbook/testing.md b/packages/docs-new/cookbook/testing.md new file mode 100644 index 00000000..dc1d8654 --- /dev/null +++ b/packages/docs-new/cookbook/testing.md @@ -0,0 +1,252 @@ +# Testing stores + +Stores will, by design, be used at many places and can make testing much harder than it should be. Fortunately, this doesn't have to be the case. We need to take care of three things when testing stores: + +- The `pinia` instance: Stores cannot work without it +- `actions`: most of the time, they contain the most complex logic of our stores. Wouldn't it be nice if they were mocked by default? +- Plugins: If you rely on plugins, you will have to install them for tests too + +Depending on what or how you are testing, we need to take care of these three differently: + +- [Testing stores](#testing-stores) + - [Unit testing a store](#unit-testing-a-store) + - [Unit testing components](#unit-testing-components) + - [Initial State](#initial-state) + - [Customizing behavior of actions](#customizing-behavior-of-actions) + - [Specifying the createSpy function](#specifying-the-createspy-function) + - [Mocking getters](#mocking-getters) + - [Pinia Plugins](#pinia-plugins) + - [E2E tests](#e2e-tests) + - [Unit test components (Vue 2)](#unit-test-components-vue-2) + +## Unit testing a store + +To unit test a store, the most important part is creating a `pinia` instance: + +```js +// stores/counter.spec.ts +import { setActivePinia, createPinia } from 'pinia' +import { useCounter } from '../src/stores/counter' + +describe('Counter Store', () => { + beforeEach(() => { + // creates a fresh pinia and make it active so it's automatically picked + // up by any useStore() call without having to pass it to it: + // `useStore(pinia)` + setActivePinia(createPinia()) + }) + + it('increments', () => { + const counter = useCounter() + expect(counter.n).toBe(0) + counter.increment() + expect(counter.n).toBe(1) + }) + + it('increments by amount', () => { + const counter = useCounter() + counter.increment(10) + expect(counter.n).toBe(10) + }) +}) +``` + +If you have any store plugins, there is one important thing to know: **plugins won't be used until `pinia` is installed in an App**. This can be solved by creating an empty App or a fake one: + +```js +import { setActivePinia, createPinia } from 'pinia' +import { createApp } from 'vue' +import { somePlugin } from '../src/stores/plugin' + +// same code as above... + +// you don't need to create one app per test +const app = createApp({}) +beforeEach(() => { + const pinia = createPinia().use(somePlugin) + app.use(pinia) + setActivePinia(pinia) +}) +``` + +## Unit testing components + +This can be achieved with `createTestingPinia()`, which returns a pinia instance designed to help unit tests components. + +Start by installing `@pinia/testing`: + +```shell +npm i -D @pinia/testing +``` + +And make sure to create a testing pinia in your tests when mounting a component: + +```js +import { mount } from '@vue/test-utils' +import { createTestingPinia } from '@pinia/testing' +// import any store you want to interact with in tests +import { useSomeStore } from '@/stores/myStore' + +const wrapper = mount(Counter, { + global: { + plugins: [createTestingPinia()], + }, +}) + +const store = useSomeStore() // uses the testing pinia! + +// state can be directly manipulated +store.name = 'my new name' +// can also be done through patch +store.$patch({ name: 'new name' }) +expect(store.name).toBe('new name') + +// actions are stubbed by default, meaning they don't execute their code by default. +// See below to customize this behavior. +store.someAction() + +expect(store.someAction).toHaveBeenCalledTimes(1) +expect(store.someAction).toHaveBeenLastCalledWith() +``` + +Please note that if you are using Vue 2, `@vue/test-utils` requires a [slightly different configuration](#unit-test-components-vue-2). + +### Initial State + +You can set the initial state of **all of your stores** when creating a testing pinia by passing an `initialState` object. This object will be used by the testing pinia to _patch_ stores when they are created. Let's say you want to initialize the state of this store: + +```ts +import { defineStore } from 'pinia' + +const useCounterStore = defineStore('counter', { + state: () => ({ n: 0 }), + // ... +}) +``` + +Since the store is named _"counter"_, you need to add a matching object to `initialState`: + +```ts +// somewhere in your test +const wrapper = mount(Counter, { + global: { + plugins: [ + createTestingPinia({ + initialState: { + counter: { n: 20 }, // start the counter at 20 instead of 0 + }, + }), + ], + }, +}) + +const store = useSomeStore() // uses the testing pinia! +store.n // 20 +``` + +### Customizing behavior of actions + +`createTestingPinia` stubs out all store actions unless told otherwise. This allows you to test your components and stores separately. + +If you want to revert this behavior and normally execute your actions during tests, specify `stubActions: false` when calling `createTestingPinia`: + +```js +const wrapper = mount(Counter, { + global: { + plugins: [createTestingPinia({ stubActions: false })], + }, +}) + +const store = useSomeStore() + +// Now this call WILL execute the implementation defined by the store +store.someAction() + +// ...but it's still wrapped with a spy, so you can inspect calls +expect(store.someAction).toHaveBeenCalledTimes(1) +``` + +### Specifying the createSpy function + +When using Jest, or vitest with `globals: true`, `createTestingPinia` automatically stubs actions using the spy function based on the existing test framework (`jest.fn` or `vitest.fn`). If you are using a different framework, you'll need to provide a [createSpy](/api/interfaces/pinia_testing.TestingOptions.html#createspy) option: + +```js +import sinon from 'sinon' + +createTestingPinia({ + createSpy: sinon.spy, // use sinon's spy to wrap actions +}) +``` + +You can find more examples in [the tests of the testing package](https://github.com/vuejs/pinia/blob/v2/packages/testing/src/testing.spec.ts). + +### Mocking getters + +By default, any getter will be computed like regular usage but you can manually force a value by setting the getter to anything you want: + +```ts +import { defineStore } from 'pinia' +import { createTestingPinia } from '@pinia/testing' + +const useCounter = defineStore('counter', { + state: () => ({ n: 1 }), + getters: { + double: (state) => state.n * 2, + }, +}) + +const pinia = createTestingPinia() +const counter = useCounter(pinia) + +counter.double = 3 // 🪄 getters are writable only in tests + +// set to undefined to reset the default behavior +// @ts-expect-error: usually it's a number +counter.double = undefined +counter.double // 2 (=1 x 2) +``` + +### Pinia Plugins + +If you have any pinia plugins, make sure to pass them when calling `createTestingPinia()` so they are properly applied. **Do not add them with `testingPinia.use(MyPlugin)`** like you would do with a regular pinia: + +```js +import { createTestingPinia } from '@pinia/testing' +import { somePlugin } from '../src/stores/plugin' + +// inside some test +const wrapper = mount(Counter, { + global: { + plugins: [ + createTestingPinia({ + stubActions: false, + plugins: [somePlugin], + }), + ], + }, +}) +``` + +## E2E tests + +When it comes to pinia, you don't need to change anything for e2e tests, that's the whole point of e2e tests! You could maybe test HTTP requests, but that's way beyond the scope of this guide 😄. + +## Unit test components (Vue 2) + +When using [Vue Test Utils 1](https://v1.test-utils.vuejs.org/), install Pinia on a `localVue`: + +```js +import { PiniaVuePlugin } from 'pinia' +import { createLocalVue, mount } from '@vue/test-utils' +import { createTestingPinia } from '@pinia/testing' + +const localVue = createLocalVue() +localVue.use(PiniaVuePlugin) + +const wrapper = mount(Counter, { + localVue, + pinia: createTestingPinia(), +}) + +const store = useSomeStore() // uses the testing pinia! +``` diff --git a/packages/docs-new/core-concepts/actions.md b/packages/docs-new/core-concepts/actions.md new file mode 100644 index 00000000..a68ee338 --- /dev/null +++ b/packages/docs-new/core-concepts/actions.md @@ -0,0 +1,234 @@ +# Actions + + + +Actions are the equivalent of [methods](https://v3.vuejs.org/guide/data-methods.html#methods) in components. They can be defined with the `actions` property in `defineStore()` and **they are perfect to define business logic**: + +```js +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + actions: { + // since we rely on `this`, we cannot use an arrow function + increment() { + this.count++ + }, + randomizeCounter() { + this.count = Math.round(100 * Math.random()) + }, + }, +}) +``` + +Like [getters](./getters.md), actions get access to the _whole store instance_ through `this` with **full typing (and autocompletion ✨) support**. **Unlike getters, `actions` can be asynchronous**, you can `await` inside of actions any API call or even other actions! Here is an example using [Mande](https://github.com/posva/mande). Note the library you use doesn't matter as long as you get a `Promise`, you could even use the native `fetch` function (browser only): + +```js +import { mande } from 'mande' + +const api = mande('/api/users') + +export const useUsers = defineStore('users', { + state: () => ({ + userData: null, + // ... + }), + + actions: { + async registerUser(login, password) { + try { + this.userData = await api.post({ login, password }) + showTooltip(`Welcome back ${this.userData.name}!`) + } catch (error) { + showTooltip(error) + // let the form component display the error + return error + } + }, + }, +}) +``` + +You are also completely free to set whatever arguments you want and return anything. When calling actions, everything will be automatically inferred! + +Actions are invoked like methods: + +```js +export default defineComponent({ + setup() { + const store = useCounterStore() + // call the action as a method of the store + store.randomizeCounter() + + return {} + }, +}) +``` + +## Accessing other stores actions + +To use another store, you can directly _use it_ inside of the _action_: + +```js +import { useAuthStore } from './auth-store' + +export const useSettingsStore = defineStore('settings', { + state: () => ({ + preferences: null, + // ... + }), + actions: { + async fetchUserPreferences() { + const auth = useAuthStore() + if (auth.isAuthenticated) { + this.preferences = await fetchPreferences() + } else { + throw new Error('User must be authenticated') + } + }, + }, +}) +``` + +## Usage with `setup()` + +You can directly call any action as a method of the store: + +```js +export default { + setup() { + const store = useCounterStore() + + store.randomizeCounter() + }, +} +``` + +## Usage with the Options API + + + +For the following examples, you can assume the following store was created: + +```js +// Example File Path: +// ./src/stores/counter.js + +import { defineStore } from 'pinia', + +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0 + }), + actions: { + increment() { + this.count++ + } + } +}) +``` + +### With `setup()` + +While Composition API is not for everyone, the `setup()` hook can make using Pinia easier to work within the Options API. No extra map helper functions needed! + +```js +import { useCounterStore } from '../stores/counter' + +export default { + setup() { + const counterStore = useCounterStore() + + return { counterStore } + }, + methods: { + incrementAndPrint() { + this.counterStore.increment() + console.log('New Count:', this.counterStore.count) + }, + }, +} +``` + +### Without `setup()` + +If you would prefer not to use Composition API at all, you can use the `mapActions()` helper to map actions properties as methods in your component: + +```js +import { mapActions } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + methods: { + // gives access to this.increment() inside the component + // same as calling from store.increment() + ...mapActions(useCounterStore, ['increment']) + // same as above but registers it as this.myOwnName() + ...mapActions(useCounterStore, { myOwnName: 'increment' }), + }, +} +``` + +## Subscribing to actions + +It is possible to observe actions and their outcome with `store.$onAction()`. The callback passed to it is executed before the action itself. `after` handle promises and allows you to execute a function after the action resolves. In a similar way, `onError` allows you execute a function if the action throws or rejects. These are useful for tracking errors at runtime, similar to [this tip in the Vue docs](https://v3.vuejs.org/guide/tooling/deployment.html#tracking-runtime-errors). + +Here is an example that logs before running actions and after they resolve/reject. + +```js +const unsubscribe = someStore.$onAction( + ({ + name, // name of the action + store, // store instance, same as `someStore` + args, // array of parameters passed to the action + after, // hook after the action returns or resolves + onError, // hook if the action throws or rejects + }) => { + // a shared variable for this specific action call + const startTime = Date.now() + // this will trigger before an action on `store` is executed + console.log(`Start "${name}" with params [${args.join(', ')}].`) + + // this will trigger if the action succeeds and after it has fully run. + // it waits for any returned promised + after((result) => { + console.log( + `Finished "${name}" after ${ + Date.now() - startTime + }ms.\nResult: ${result}.` + ) + }) + + // this will trigger if the action throws or returns a promise that rejects + onError((error) => { + console.warn( + `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.` + ) + }) + } +) + +// manually remove the listener +unsubscribe() +``` + +By default, _action subscriptions_ are bound to the component where they are added (if the store is inside a component's `setup()`). Meaning, they will be automatically removed when the component is unmounted. If you also want to keep them after the component is unmounted, pass `true` as the second argument to _detach_ the _action subscription_ from the current component: + +```js +export default { + setup() { + const someStore = useSomeStore() + + // this subscription will be kept even after the component is unmounted + someStore.$onAction(callback, true) + + // ... + }, +} +``` diff --git a/packages/docs-new/core-concepts/getters.md b/packages/docs-new/core-concepts/getters.md new file mode 100644 index 00000000..6ecb9f3a --- /dev/null +++ b/packages/docs-new/core-concepts/getters.md @@ -0,0 +1,236 @@ +# Getters + + + +Getters are exactly the equivalent of [computed values](https://vuejs.org/guide/essentials/computed.html) for the state of a Store. They can be defined with the `getters` property in `defineStore()`. They receive the `state` as the first parameter **to encourage** the usage of arrow function: + +```js +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + getters: { + doubleCount: (state) => state.count * 2, + }, +}) +``` + +Most of the time, getters will only rely on the state, however, they might need to use other getters. Because of this, we can get access to the _whole store instance_ through `this` when defining a regular function **but it is necessary to define the type of the return type (in TypeScript)**. This is due to a known limitation in TypeScript and **doesn't affect getters defined with an arrow function nor getters not using `this`**: + +```ts +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + getters: { + // automatically infers the return type as a number + doubleCount(state) { + return state.count * 2 + }, + // the return type **must** be explicitly set + doublePlusOne(): number { + // autocompletion and typings for the whole store ✨ + return this.doubleCount + 1 + }, + }, +}) +``` + +Then you can access the getter directly on the store instance: + +```vue + + + +``` + +## Accessing other getters + +As with computed properties, you can combine multiple getters. Access any other getter via `this`. Even if you are not using TypeScript, you can hint your IDE for types with the [JSDoc](https://jsdoc.app/tags-returns.html): + +```js +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + getters: { + // type is automatically inferred because we are not using `this` + doubleCount: (state) => state.count * 2, + // here we need to add the type ourselves (using JSDoc in JS). We can also + // use this to document the getter + /** + * Returns the count value times two plus one. + * + * @returns {number} + */ + doubleCountPlusOne() { + // autocompletion ✨ + return this.doubleCount + 1 + }, + }, +}) +``` + +## Passing arguments to getters + +_Getters_ are just _computed_ properties behind the scenes, so it's not possible to pass any parameters to them. However, you can return a function from the _getter_ to accept any arguments: + +```js +export const useStore = defineStore('main', { + getters: { + getUserById: (state) => { + return (userId) => state.users.find((user) => user.id === userId) + }, + }, +}) +``` + +and use in component: + +```vue + + + +``` + +Note that when doing this, **getters are not cached anymore**, they are simply functions that you invoke. You can however cache some results inside of the getter itself, which is uncommon but should prove more performant: + +```js +export const useStore = defineStore('main', { + getters: { + getActiveUserById(state) { + const activeUsers = state.users.filter((user) => user.active) + return (userId) => activeUsers.find((user) => user.id === userId) + }, + }, +}) +``` + +## Accessing other stores getters + +To use another store getters, you can directly _use it_ inside of the _getter_: + +```js +import { useOtherStore } from './other-store' + +export const useStore = defineStore('main', { + state: () => ({ + // ... + }), + getters: { + otherGetter(state) { + const otherStore = useOtherStore() + return state.localData + otherStore.data + }, + }, +}) +``` + +## Usage with `setup()` + +You can directly access any getter as a property of the store (exactly like state properties): + +```js +export default { + setup() { + const store = useCounterStore() + + store.count = 3 + store.doubleCount // 6 + }, +} +``` + +## Usage with the Options API + + + +For the following examples, you can assume the following store was created: + +```js +// Example File Path: +// ./src/stores/counter.js + +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + getters: { + doubleCount(state) { + return state.count * 2 + }, + }, +}) +``` + +### With `setup()` + +While Composition API is not for everyone, the `setup()` hook can make using Pinia easier to work with in the Options API. No extra map helper functions needed! + +```js +import { useCounterStore } from '../stores/counter' + +export default { + setup() { + const counterStore = useCounterStore() + + return { counterStore } + }, + computed: { + quadrupleCounter() { + return this.counterStore.doubleCount * 2 + }, + }, +} +``` + +### Without `setup()` + +You can use the same `mapState()` function used in the [previous section of state](./state.md#options-api) to map to getters: + +```js +import { mapState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // gives access to this.doubleCount inside the component + // same as reading from store.doubleCount + ...mapState(useCounterStore, ['doubleCount']), + // same as above but registers it as this.myOwnName + ...mapState(useCounterStore, { + myOwnName: 'doubleCount', + // you can also write a function that gets access to the store + double: (store) => store.doubleCount, + }), + }, +} +``` diff --git a/packages/docs-new/core-concepts/index.md b/packages/docs-new/core-concepts/index.md new file mode 100644 index 00000000..41b7b62a --- /dev/null +++ b/packages/docs-new/core-concepts/index.md @@ -0,0 +1,158 @@ +# Defining a Store + + + +Before diving into core concepts, we need to know that a store is defined using `defineStore()` and that it requires a **unique** name, passed as the first argument: + +```js +import { defineStore } from 'pinia' + +// You can name the return value of `defineStore()` anything you want, +// but it's best to use the name of the store and surround it with `use` +// and `Store` (e.g. `useUserStore`, `useCartStore`, `useProductStore`) +// the first argument is a unique id of the store across your application +export const useAlertsStore = defineStore('alerts', { + // other options... +}) +``` + +This _name_, also referred to as _id_, is necessary and is used by Pinia to connect the store to the devtools. Naming the returned function _use..._ is a convention across composables to make its usage idiomatic. + +`defineStore()` accepts two distinct values for its second argument: a Setup function or an Options object. + +## Option Stores + +Similar to Vue's Options API, we can also pass an Options Object with `state`, `actions`, and `getters` properties. + +```js {2-10} +export const useCounterStore = defineStore('counter', { + state: () => ({ count: 0, name: 'Eduardo' }), + getters: { + doubleCount: (state) => state.count * 2, + }, + actions: { + increment() { + this.count++ + }, + }, +}) +``` + +You can think of `state` as the `data` of the store, `getters` as the `computed` properties of the store, and `actions` as the `methods`. + +Option stores should feel intuitive and simple to get started with. + +## Setup Stores + +There is also another possible syntax to define stores. Similar to the Vue Composition API's [setup function](https://vuejs.org/api/composition-api-setup.html), we can pass in a function that defines reactive properties and methods and returns an object with the properties and methods we want to expose. + +```js +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const name = ref('Eduardo') + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, name, doubleCount, increment } +}) +``` + +In _Setup Stores_: + +- `ref()`s become `state` properties +- `computed()`s become `getters` +- `function()`s become `actions` + +Setup stores bring a lot more flexibility than [Option Stores](#option-stores) as you can create watchers within a store and freely use any [composable](https://vuejs.org/guide/reusability/composables.html#composables). However, keep in mind that using composables will get more complex when using [SSR](../cookbook/composables.md). + +## What syntax should I pick? + +As with [Vue's Composition API and Options API](https://vuejs.org/guide/introduction.html#which-to-choose), pick the one that you feel the most comfortable with. If you're not sure, try [Option Stores](#option-stores) first. + +## Using the store + +We are _defining_ a store because the store won't be created until `use...Store()` is called inside of `setup()`: + +```js +import { useCounterStore } from '@/stores/counter' + +export default { + setup() { + const store = useCounterStore() + + return { + // you can return the whole store instance to use it in the template + store, + } + }, +} +``` + +:::tip +If you are not using `setup` components yet, [you can still use Pinia with _map helpers_](../cookbook/options-api.md). +::: + +You can define as many stores as you want and **you should define each store in a different file** to get the most out of Pinia (like automatically allowing your bundler to code split and providing TypeScript inference). + +Once the store is instantiated, you can access any property defined in `state`, `getters`, and `actions` directly on the store. We will look at these in detail in the next pages but autocompletion will help you. + +Note that `store` is an object wrapped with `reactive`, meaning there is no need to write `.value` after getters but, like `props` in `setup`, **we cannot destructure it**: + +```js +export default defineComponent({ + setup() { + const store = useCounterStore() + // ❌ This won't work because it breaks reactivity + // it's the same as destructuring from `props` + const { name, doubleCount } = store + + name // "Eduardo" + doubleCount // 0 + + setTimeout(() => { + store.increment() + }, 1000) + + return { + // will always be "Eduardo" + name, + // will always be 0 + doubleCount, + // will also always be 0 + doubleNumber: store.doubleCount, + + // ✅ this one will be reactive + doubleValue: computed(() => store.doubleCount), + } + }, +}) +``` + +In order to extract properties from the store while keeping its reactivity, you need to use `storeToRefs()`. It will create refs for every reactive property. This is useful when you are only using state from the store but not calling any action. Note you can destructure actions directly from the store as they are bound to the store itself too: + +```js +import { storeToRefs } from 'pinia' + +export default defineComponent({ + setup() { + const store = useCounterStore() + // `name` and `doubleCount` are reactive refs + // This will also create refs for properties added by plugins + // but skip any action or non reactive (non ref/reactive) property + const { name, doubleCount } = storeToRefs(store) + // the increment action can just be extracted + const { increment } = store + + return { + name, + doubleCount, + increment, + } + }, +}) +``` diff --git a/packages/docs-new/core-concepts/outside-component-usage.md b/packages/docs-new/core-concepts/outside-component-usage.md new file mode 100644 index 00000000..f9c3d95e --- /dev/null +++ b/packages/docs-new/core-concepts/outside-component-usage.md @@ -0,0 +1,59 @@ +# Using a store outside of a component + +Pinia stores rely on the `pinia` instance to share the same store instance across all calls. Most of the time, this works out of the box by just calling your `useStore()` function. For example, in `setup()`, you don't need to do anything else. But things are a bit different outside of a component. +Behind the scenes, `useStore()` _injects_ the `pinia` instance you gave to your `app`. This means that if the `pinia` instance cannot be automatically injected, you have to manually provide it to the `useStore()` function. +You can solve this differently depending on the kind of application you are writing. + +## Single Page Applications + +If you are not doing any SSR (Server Side Rendering), any call of `useStore()` after installing the pinia plugin with `app.use(pinia)` will work: + +```js +import { useUserStore } from '@/stores/user' +import { createApp } from 'vue' +import App from './App.vue' + +// ❌ fails because it's called before the pinia is created +const userStore = useUserStore() + +const pinia = createPinia() +const app = createApp(App) +app.use(pinia) + +// ✅ works because the pinia instance is now active +const userStore = useUserStore() +``` + +The easiest way to ensure this is always applied is to _defer_ calls of `useStore()` by placing them inside functions that will always run after pinia is installed. + +Let's take a look at this example of using a store inside of a navigation guard with Vue Router: + +```js +import { createRouter } from 'vue-router' +const router = createRouter({ + // ... +}) + +// ❌ Depending on the order of imports this will fail +const store = useStore() + +router.beforeEach((to, from, next) => { + // we wanted to use the store here + if (store.isLoggedIn) next() + else next('/login') +}) + +router.beforeEach((to) => { + // ✅ This will work because the router starts its navigation after + // the router is installed and pinia will be installed too + const store = useStore() + + if (to.meta.requiresAuth && !store.isLoggedIn) return '/login' +}) +``` + +## SSR Apps + +When dealing with Server Side Rendering, you will have to pass the `pinia` instance to `useStore()`. This prevents pinia from sharing global state between different application instances. + +There is a whole section dedicated to it in the [SSR guide](/ssr/index.md), this is just a short explanation: diff --git a/packages/docs-new/core-concepts/plugins.md b/packages/docs-new/core-concepts/plugins.md new file mode 100644 index 00000000..010fb16f --- /dev/null +++ b/packages/docs-new/core-concepts/plugins.md @@ -0,0 +1,413 @@ +# Plugins + +Pinia stores can be fully extended thanks to a low level API. Here is a list of things you can do: + +- Add new properties to stores +- Add new options when defining stores +- Add new methods to stores +- Wrap existing methods +- Change or even cancel actions +- Implement side effects like [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) +- Apply **only** to specific stores + +Plugins are added to the pinia instance with `pinia.use()`. The simplest example is adding a static property to all stores by returning an object: + +```js +import { createPinia } from 'pinia' + +// add a property named `secret` to every store that is created after this plugin is installed +// this could be in a different file +function SecretPiniaPlugin() { + return { secret: 'the cake is a lie' } +} + +const pinia = createPinia() +// give the plugin to pinia +pinia.use(SecretPiniaPlugin) + +// in another file +const store = useStore() +store.secret // 'the cake is a lie' +``` + +This is useful to add global objects like the router, modal, or toast managers. + +## Introduction + +A Pinia plugin is a function that optionally returns properties to be added to a store. It takes one optional argument, a _context_: + +```js +export function myPiniaPlugin(context) { + context.pinia // the pinia created with `createPinia()` + context.app // the current app created with `createApp()` (Vue 3 only) + context.store // the store the plugin is augmenting + context.options // the options object defining the store passed to `defineStore()` + // ... +} +``` + +This function is then passed to `pinia` with `pinia.use()`: + +```js +pinia.use(myPiniaPlugin) +``` + +Plugins are only applied to stores created **after the plugins themselves, and after `pinia` is passed to the app**, otherwise they won't be applied. + +## Augmenting a Store + +You can add properties to every store by simply returning an object of them in a plugin: + +```js +pinia.use(() => ({ hello: 'world' })) +``` + +You can also set the property directly on the `store` but **if possible use the return version so they can be automatically tracked by devtools**: + +```js +pinia.use(({ store }) => { + store.hello = 'world' +}) +``` + +Any property _returned_ by a plugin will be automatically tracked by devtools so in order to make `hello` visible in devtools, make sure to add it to `store._customProperties` **in dev mode only** if you want to debug it in devtools: + +```js +// from the example above +pinia.use(({ store }) => { + store.hello = 'world' + // make sure your bundler handle this. webpack and vite should do it by default + if (process.env.NODE_ENV === 'development') { + // add any keys you set on the store + store._customProperties.add('hello') + } +}) +``` + +Note that every store is wrapped with [`reactive`](https://v3.vuejs.org/api/basic-reactivity.html#reactive), automatically unwrapping any Ref (`ref()`, `computed()`, ...) it contains: + +```js +const sharedRef = ref('shared') +pinia.use(({ store }) => { + // each store has its individual `hello` property + store.hello = ref('secret') + // it gets automatically unwrapped + store.hello // 'secret' + + // all stores are sharing the value `shared` property + store.shared = sharedRef + store.shared // 'shared' +}) +``` + +This is why you can access all computed properties without `.value` and why they are reactive. + +### Adding new state + +If you want to add new state properties to a store or properties that are meant to be used during hydration, **you will have to add it in two places**: + +- On the `store` so you can access it with `store.myState` +- On `store.$state` so it can be used in devtools and, **be serialized during SSR**. + +On top of that, you will certainly have to use a `ref()` (or other reactive API) in order to share the value across different accesses: + +```js +import { toRef, ref } from 'vue' + +pinia.use(({ store }) => { + // to correctly handle SSR, we need to make sure we are not overriding an + // existing value + if (!Object.prototype.hasOwnProperty(store.$state, 'hasError')) { + // hasError is defined within the plugin, so each store has their individual + // state property + const hasError = ref(false) + // setting the variable on `$state`, allows it be serialized during SSR + store.$state.hasError = hasError + } + // we need to transfer the ref from the state to the store, this way + // both accesses: store.hasError and store.$state.hasError will work + // and share the same variable + // See https://vuejs.org/api/reactivity-utilities.html#toref + store.hasError = toRef(store.$state, 'hasError') + + // in this case it's better not to return `hasError` since it + // will be displayed in the `state` section in the devtools + // anyway and if we return it, devtools will display it twice. +}) +``` + +Note that state changes or additions that occur within a plugin (that includes calling `store.$patch()`) happen before the store is active and therefore **do not trigger any subscriptions**. + +:::warning +If you are using **Vue 2**, Pinia is subject to the [same reactivity caveats](https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) as Vue. You will need to use `Vue.set()` (Vue 2.7) or `set()` (from `@vue/composition-api` for Vue <2.7) for when creating new state properties like `secret` and `hasError`: + +```js +import { set, toRef } from '@vue/composition-api' +pinia.use(({ store }) => { + if (!Object.prototype.hasOwnProperty(store.$state, 'hello')) { + const secretRef = ref('secret') + // If the data is meant to be used during SSR, you should + // set it on the `$state` property so it is serialized and + // picked up during hydration + set(store.$state, 'secret', secretRef) + } + // set it directly on the store too so you can access it + // both ways: `store.$state.secret` / `store.secret` + set(store, 'secret', toRef(store.$state, 'secret')) + store.secret // 'secret' +}) +``` + +::: + +## Adding new external properties + +When adding external properties, class instances that come from other libraries, or simply things that are not reactive, you should wrap the object with `markRaw()` before passing it to pinia. Here is an example adding the router to every store: + +```js +import { markRaw } from 'vue' +// adapt this based on where your router is +import { router } from './router' + +pinia.use(({ store }) => { + store.router = markRaw(router) +}) +``` + +## Calling `$subscribe` inside plugins + +You can use [store.$subscribe](./state.md#subscribing-to-the-state) and [store.$onAction](./actions.md#subscribing-to-actions) inside plugins too: + +```ts +pinia.use(({ store }) => { + store.$subscribe(() => { + // react to store changes + }) + store.$onAction(() => { + // react to store actions + }) +}) +``` + +## Adding new options + +It is possible to create new options when defining stores to later on consume them from plugins. For example, you could create a `debounce` option that allows you to debounce any action: + +```js +defineStore('search', { + actions: { + searchContacts() { + // ... + }, + }, + + // this will be read by a plugin later on + debounce: { + // debounce the action searchContacts by 300ms + searchContacts: 300, + }, +}) +``` + +The plugin can then read that option to wrap actions and replace the original ones: + +```js +// use any debounce library +import debounce from 'lodash/debounce' + +pinia.use(({ options, store }) => { + if (options.debounce) { + // we are overriding the actions with new ones + return Object.keys(options.debounce).reduce((debouncedActions, action) => { + debouncedActions[action] = debounce( + store[action], + options.debounce[action] + ) + return debouncedActions + }, {}) + } +}) +``` + +Note that custom options are passed as the 3rd argument when using the setup syntax: + +```js +defineStore( + 'search', + () => { + // ... + }, + { + // this will be read by a plugin later on + debounce: { + // debounce the action searchContacts by 300ms + searchContacts: 300, + }, + } +) +``` + +## TypeScript + +Everything shown above can be done with typing support, so you don't ever need to use `any` or `@ts-ignore`. + +### Typing plugins + +A Pinia plugin can be typed as follows: + +```ts +import { PiniaPluginContext } from 'pinia' + +export function myPiniaPlugin(context: PiniaPluginContext) { + // ... +} +``` + +### Typing new store properties + +When adding new properties to stores, you should also extend the `PiniaCustomProperties` interface. + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomProperties { + // by using a setter we can allow both strings and refs + set hello(value: string | Ref) + get hello(): string + + // you can define simpler values too + simpleNumber: number + } +} +``` + +It can then be written and read safely: + +```ts +pinia.use(({ store }) => { + store.hello = 'Hola' + store.hello = ref('Hola') + + store.simpleNumber = Math.random() + // @ts-expect-error: we haven't typed this correctly + store.simpleNumber = ref(Math.random()) +}) +``` + +`PiniaCustomProperties` is a generic type that allows you to reference properties of a store. Imagine the following example where we copy over the initial options as `$options` (this would only work for option stores): + +```ts +pinia.use(({ options }) => ({ $options: options })) +``` + +We can properly type this by using the 4 generic types of `PiniaCustomProperties`: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomProperties { + $options: { + id: Id + state?: () => S + getters?: G + actions?: A + } + } +} +``` + +:::tip +When extending types in generics, they must be named **exactly as in the source code**. `Id` cannot be named `id` or `I`, and `S` cannot be named `State`. Here is what every letter stands for: + +- S: State +- G: Getters +- A: Actions +- SS: Setup Store / Store + +::: + +### Typing new state + +When adding new state properties (to both, the `store` and `store.$state`), you need to add the type to `PiniaCustomStateProperties` instead. Differently from `PiniaCustomProperties`, it only receives the `State` generic: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomStateProperties { + hello: string + } +} +``` + +### Typing new creation options + +When creating new options for `defineStore()`, you should extend the `DefineStoreOptionsBase`. Differently from `PiniaCustomProperties`, it only exposes two generics: the State and the Store type, allowing you to limit what can be defined. For example, you can use the names of the actions: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface DefineStoreOptionsBase { + // allow defining a number of ms for any of the actions + debounce?: Partial, number>> + } +} +``` + +:::tip +There is also a `StoreGetters` type to extract the _getters_ from a Store type. You can also extend the options of _setup stores_ or _option stores_ **only** by extending the types `DefineStoreOptions` and `DefineSetupStoreOptions` respectively. +::: + +## Nuxt.js + +When [using pinia alongside Nuxt](../ssr/nuxt.md), you will have to create a [Nuxt plugin](https://nuxtjs.org/docs/2.x/directory-structure/plugins) first. This will give you access to the `pinia` instance: + +```ts +// plugins/myPiniaPlugin.ts +import { PiniaPluginContext } from 'pinia' + +function MyPiniaPlugin({ store }: PiniaPluginContext) { + store.$subscribe((mutation) => { + // react to store changes + console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`) + }) + + // Note this has to be typed if you are using TS + return { creationTime: new Date() } +} + +export default defineNuxtPlugin(({ $pinia }) => { + $pinia.use(MyPiniaPlugin) +}) +``` + +Note the above example is using TypeScript, you have to remove the type annotations `PiniaPluginContext` and `Plugin` as well as their imports if you are using a `.js` file. + +### Nuxt.js 2 + +If you are using Nuxt.js 2, the types are slightly different: + +```ts +// plugins/myPiniaPlugin.ts +import { PiniaPluginContext } from 'pinia' +import { Plugin } from '@nuxt/types' + +function MyPiniaPlugin({ store }: PiniaPluginContext) { + store.$subscribe((mutation) => { + // react to store changes + console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`) + }) + + // Note this has to be typed if you are using TS + return { creationTime: new Date() } +} + +const myPlugin: Plugin = ({ $pinia }) => { + $pinia.use(MyPiniaPlugin) +} + +export default myPlugin +``` diff --git a/packages/docs-new/core-concepts/state.md b/packages/docs-new/core-concepts/state.md new file mode 100644 index 00000000..92adef3b --- /dev/null +++ b/packages/docs-new/core-concepts/state.md @@ -0,0 +1,263 @@ +# State + + + +The state is, most of the time, the central part of your store. People often start by defining the state that represents their app. In Pinia the state is defined as a function that returns the initial state. This allows Pinia to work in both Server and Client Side. + +```js +import { defineStore } from 'pinia' + +export const useStore = defineStore('storeId', { + // arrow function recommended for full type inference + state: () => { + return { + // all these properties will have their type inferred automatically + count: 0, + name: 'Eduardo', + isAdmin: true, + items: [], + hasChanged: true, + } + }, +}) +``` + +:::tip +If you are using Vue 2, the data you create in `state` follows the same rules as the `data` in a Vue instance, i.e. the state object must be plain and you need to call `Vue.set()` when **adding new** properties to it. **See also: [Vue#data](https://v2.vuejs.org/v2/api/#data)**. +::: + +## TypeScript + +You don't need to do much in order to make your state compatible with TS: make sure [`strict`](https://www.typescriptlang.org/tsconfig#strict), or at the very least, [`noImplicitThis`](https://www.typescriptlang.org/tsconfig#noImplicitThis), are enabled and Pinia will infer the type of your state automatically! However, there are a few cases where you should give it a hand with some casting: + +```ts +export const useUserStore = defineStore('user', { + state: () => { + return { + // for initially empty lists + userList: [] as UserInfo[], + // for data that is not yet loaded + user: null as UserInfo | null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` + +If you prefer, you can define the state with an interface and type the return value of `state()`: + +```ts +interface State { + userList: UserInfo[] + user: UserInfo | null +} + +export const useUserStore = defineStore('user', { + state: (): State => { + return { + userList: [], + user: null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` + +## Accessing the `state` + +By default, you can directly read and write to the state by accessing it through the `store` instance: + +```js +const store = useStore() + +store.count++ +``` + +Note you cannot add a new state property **if you don't define it in `state()`**, it must contain the initial state. e.g.: we can't do `store.secondCount = 2` if `secondCount` is not defined in `state()`. + +## Resetting the state + +You can _reset_ the state to its initial value by calling the `$reset()` method on the store: + +```js +const store = useStore() + +store.$reset() +``` + +### Usage with the Options API + + + +For the following examples, you can assume the following store was created: + +```js +// Example File Path: +// ./src/stores/counter.js + +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), +}) +``` + +If you are not using the Composition API, and you are using `computed`, `methods`, ..., you can use the `mapState()` helper to map state properties as readonly computed properties: + +```js +import { mapState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // gives access to this.count inside the component + // same as reading from store.count + ...mapState(useCounterStore, ['count']) + // same as above but registers it as this.myOwnName + ...mapState(useCounterStore, { + myOwnName: 'count', + // you can also write a function that gets access to the store + double: store => store.count * 2, + // it can have access to `this` but it won't be typed correctly... + magicValue(store) { + return store.someGetter + this.count + this.double + }, + }), + }, +} +``` + +#### Modifiable state + +If you want to be able to write to these state properties (e.g. if you have a form), you can use `mapWritableState()` instead. Note you cannot pass a function like with `mapState()`: + +```js +import { mapWritableState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // gives access to this.count inside the component and allows setting it + // this.count++ + // same as reading from store.count + ...mapWritableState(useCounterStore, ['count']) + // same as above but registers it as this.myOwnName + ...mapWritableState(useCounterStore, { + myOwnName: 'count', + }), + }, +} +``` + +:::tip +You don't need `mapWritableState()` for collections like arrays unless you are replacing the whole array with `cartItems = []`, `mapState()` still allows you to call methods on your collections. +::: + +## Mutating the state + + + +Apart from directly mutating the store with `store.count++`, you can also call the `$patch` method. It allows you to apply multiple changes at the same time with a partial `state` object: + +```js +store.$patch({ + count: store.count + 1, + age: 120, + name: 'DIO', +}) +``` + +However, some mutations are really hard or costly to apply with this syntax: any collection modification (e.g. pushing, removing, splicing an element from an array) requires you to create a new collection. Because of this, the `$patch` method also accepts a function to group this kind of mutations that are difficult to apply with a patch object: + +```js +store.$patch((state) => { + state.items.push({ name: 'shoes', quantity: 1 }) + state.hasChanged = true +}) +``` + + + +The main difference here is that `$patch()` allows you to group multiple changes into one single entry in the devtools. Note **both, direct changes to `state` and `$patch()` appear in the devtools** and can be time traveled (not yet in Vue 3). + +## Replacing the `state` + +You **cannot exactly replace** the state of a store as that would break reactivity. You can however _patch it_: + +```js +// this doesn't actually replace `$state` +store.$state = { count: 24 } +// it internally calls `$patch()`: +store.$patch({ count: 24 }) +``` + +You can also **set the initial state** of your whole application by changing the `state` of the `pinia` instance. This is used during [SSR for hydration](../ssr/#state-hydration). + +```js +pinia.state.value = {} +``` + +## Subscribing to the state + +You can watch the state and its changes through the `$subscribe()` method of a store, similar to Vuex's [subscribe method](https://vuex.vuejs.org/api/#subscribe). The advantage of using `$subscribe()` over a regular `watch()` is that _subscriptions_ will trigger only once after _patches_ (e.g. when using the function version from above). + +```js +cartStore.$subscribe((mutation, state) => { + // import { MutationType } from 'pinia' + mutation.type // 'direct' | 'patch object' | 'patch function' + // same as cartStore.$id + mutation.storeId // 'cart' + // only available with mutation.type === 'patch object' + mutation.payload // patch object passed to cartStore.$patch() + + // persist the whole state to the local storage whenever it changes + localStorage.setItem('cart', JSON.stringify(state)) +}) +``` + +By default, _state subscriptions_ are bound to the component where they are added (if the store is inside a component's `setup()`). Meaning, they will be automatically removed when the component is unmounted. If you also want to keep them after the component is unmounted, pass `{ detached: true }` as the second argument to _detach_ the _state subscription_ from the current component: + +```js +export default { + setup() { + const someStore = useSomeStore() + + // this subscription will be kept even after the component is unmounted + someStore.$subscribe(callback, { detached: true }) + + // ... + }, +} +``` + +:::tip +You can watch the whole state on the `pinia` instance: + +```js +watch( + pinia.state, + (state) => { + // persist the whole state to the local storage whenever it changes + localStorage.setItem('piniaState', JSON.stringify(state)) + }, + { deep: true } +) +``` + +::: diff --git a/packages/docs-new/getting-started.md b/packages/docs-new/getting-started.md new file mode 100644 index 00000000..0a8a7cee --- /dev/null +++ b/packages/docs-new/getting-started.md @@ -0,0 +1,66 @@ +# Getting Started + +## Installation + + + + +Install `pinia` with your favorite package manager: + +```bash +yarn add pinia +# or with npm +npm install pinia +``` + +:::tip +If your app is using Vue <2.7, you also need to install the composition api: `@vue/composition-api`. If you are using Nuxt, you should follow [these instructions](/ssr/nuxt.md). +::: + +If you are using the Vue CLI, you can instead give this [**unofficial plugin**](https://github.com/wobsoriano/vue-cli-plugin-pinia) a try. + +Create a pinia instance (the root store) and pass it to the app as a plugin: + +```js {2,5-6,8} +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' + +const pinia = createPinia() +const app = createApp(App) + +app.use(pinia) +app.mount('#app') +``` + +If you are using Vue 2, you also need to install a plugin and inject the created `pinia` at the root of the app: + +```js {1,3-4,12} +import { createPinia, PiniaVuePlugin } from 'pinia' + +Vue.use(PiniaVuePlugin) +const pinia = createPinia() + +new Vue({ + el: '#app', + // other options... + // ... + // note the same `pinia` instance can be used across multiple Vue apps on + // the same page + pinia, +}) +``` + +This will also add devtools support. In Vue 3, some features like time traveling and editing are still not supported because vue-devtools doesn't expose the necessary APIs yet but the devtools have way more features and the developer experience as a whole is far superior. In Vue 2, Pinia uses the existing interface for Vuex (and can therefore not be used alongside it). + +## What is a Store? + +A Store (like Pinia) is an entity holding state and business logic that isn't bound to your Component tree. In other words, **it hosts global state**. It's a bit like a component that is always there and that everybody can read off and write to. It has **three concepts**, the [state](./core-concepts/state.md), [getters](./core-concepts/getters.md) and [actions](./core-concepts/actions.md) and it's safe to assume these concepts are the equivalent of `data`, `computed` and `methods` in components. + +## When should I use a Store + +A store should contain data that can be accessed throughout your application. This includes data that is used in many places, e.g. User information that is displayed in the navbar, as well as data that needs to be preserved through pages, e.g. a very complicated multi-step form. + +On the other hand, you should avoid including in the store local data that could be hosted in a component instead, e.g. the visibility of an element local to a page. + +Not all applications need access to a global state, but if yours need one, Pinia will make your life easier. diff --git a/packages/docs-new/index.md b/packages/docs-new/index.md new file mode 100644 index 00000000..7a43ec5a --- /dev/null +++ b/packages/docs-new/index.md @@ -0,0 +1,42 @@ +--- +layout: home + +title: Pinia +titleTemplate: The intuitive store for Vue.js + +hero: + name: Pinia + text: The intuitive store for Vue.js + tagline: Type Safe, Extensible, and Modular by design. Forget you are even using a store. + image: + src: /logo.svg + alt: Pinia + actions: + - theme: brand + text: Get Started + link: /introduction + - theme: alt + text: Demo + link: https://stackblitz.com/github/piniajs/example-vue-3-vite + + +features: + - title: 💡 Intuitive + details: Stores are as familiar as components. API designed to let you write well organized stores. + - title: 🔑 Type Safe + details: Types are inferred, which means stores provide you with autocompletion even in JavaScript! + - title: ⚙️ Devtools support + details: Pinia hooks into Vue devtools to give you an enhanced development experience in both Vue 2 and Vue 3. + - title: 🔌 Extensible + details: React to store changes to extend Pinia with transactions, local storage synchronization, etc. + - title: 🏗 Modular by design + details: Build multiple stores and let your bundler code split them automatically. + - title: 📦 Extremely light + details: Pinia weighs ~1.5kb, you will forget it's even there! +--- + + + + diff --git a/packages/docs-new/introduction.md b/packages/docs-new/introduction.md new file mode 100644 index 00000000..b22ac85f --- /dev/null +++ b/packages/docs-new/introduction.md @@ -0,0 +1,195 @@ +# Introduction + + + +Pinia [started](https://github.com/vuejs/pinia/commit/06aeef54e2cad66696063c62829dac74e15fd19e) as an experiment to redesign what a Store for Vue could look like with the [Composition API](https://github.com/vuejs/composition-api) around November 2019. Since then, the initial principles have remained the same, but Pinia works for both Vue 2 and Vue 3 **and doesn't require you to use the composition API**. The API is the same for both except for _installation_ and _SSR_, and these docs are targeted to Vue 3 **with notes about Vue 2** whenever necessary so it can be read by Vue 2 and Vue 3 users! + +## Why should I use Pinia? + +Pinia is a store library for Vue, it allows you to share a state across components/pages. If you are familiar with the Composition API, you might be thinking you can already share a global state with a simple `export const state = reactive({})`. This is true for single page applications but **exposes your application to [security vulnerabilities](https://vuejs.org/guide/scaling-up/ssr.html#cross-request-state-pollution)** if it is server side rendered. But even in small single page applications, you get a lot from using Pinia: + +- Devtools support + - A timeline to track actions, mutations + - Stores appear in components where they are used + - Time travel and easier debugging +- Hot module replacement + - Modify your stores without reloading your page + - Keep any existing state while developing +- Plugins: extend Pinia features with plugins +- Proper TypeScript support or **autocompletion** for JS users +- Server Side Rendering Support + + + + +## Basic example + +This is what using Pinia looks like in terms of API (make sure to check the [Getting Started](./getting-started.md) for complete instructions). You start by creating a store: + +```js +// stores/counter.js +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', { + state: () => { + return { count: 0 } + }, + // could also be defined as + // state: () => ({ count: 0 }) + actions: { + increment() { + this.count++ + }, + }, +}) +``` + +And then you _use_ it in a component: + +```js +import { useCounterStore } from '@/stores/counter' + +export default { + setup() { + const counter = useCounterStore() + + counter.count++ + // with autocompletion ✨ + counter.$patch({ count: counter.count + 1 }) + // or using an action instead + counter.increment() + }, +} +``` + +You can even use a function (similar to a component `setup()`) to define a Store for more advanced use cases: + +```js +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + function increment() { + count.value++ + } + + return { count, increment } +}) +``` + +If you are still not into `setup()` and Composition API, don't worry, Pinia also supports a similar set of [_map helpers_ like Vuex](https://vuex.vuejs.org/guide/state.html#the-mapstate-helper). You define stores the same way but then use `mapStores()`, `mapState()`, or `mapActions()`: + +```js {22,24,28} +const useCounterStore = defineStore('counter', { + state: () => ({ count: 0 }), + getters: { + double: (state) => state.count * 2, + }, + actions: { + increment() { + this.count++ + }, + }, +}) + +const useUserStore = defineStore('user', { + // ... +}) + +export default { + computed: { + // other computed properties + // ... + // gives access to this.counterStore and this.userStore + ...mapStores(useCounterStore, useUserStore), + // gives read access to this.count and this.double + ...mapState(useCounterStore, ['count', 'double']), + }, + methods: { + // gives access to this.increment() + ...mapActions(useCounterStore, ['increment']), + }, +} +``` + +You will find more information about each _map helper_ in the core concepts. + +## Why _Pinia_ + +Pinia (pronounced `/piːnjʌ/`, like "peenya" in English) is the closest word to _piña_ (_pineapple_ in Spanish) that is a valid package name. A pineapple is in reality a group of individual flowers that join together to create a multiple fruit. Similar to stores, each one is born individually, but they are all connected at the end. It's also a delicious tropical fruit indigenous to South America. + +## A more realistic example + +Here is a more complete example of the API you will be using with Pinia **with types even in JavaScript**. For some people, this might be enough to get started without reading further but we still recommend checking the rest of the documentation or even skipping this example and coming back once you have read about all of the _Core Concepts_. + +```js +import { defineStore } from 'pinia' + +export const useTodos = defineStore('todos', { + state: () => ({ + /** @type {{ text: string, id: number, isFinished: boolean }[]} */ + todos: [], + /** @type {'all' | 'finished' | 'unfinished'} */ + filter: 'all', + // type will be automatically inferred to number + nextId: 0, + }), + getters: { + finishedTodos(state) { + // autocompletion! ✨ + return state.todos.filter((todo) => todo.isFinished) + }, + unfinishedTodos(state) { + return state.todos.filter((todo) => !todo.isFinished) + }, + /** + * @returns {{ text: string, id: number, isFinished: boolean }[]} + */ + filteredTodos(state) { + if (this.filter === 'finished') { + // call other getters with autocompletion ✨ + return this.finishedTodos + } else if (this.filter === 'unfinished') { + return this.unfinishedTodos + } + return this.todos + }, + }, + actions: { + // any amount of arguments, return a promise or not + addTodo(text) { + // you can directly mutate the state + this.todos.push({ text, id: this.nextId++, isFinished: false }) + }, + }, +}) +``` + +## Comparison with Vuex + +Pinia started out as an exploration of what the next iteration of Vuex could look like, incorporating many ideas from core team discussions for Vuex 5. Eventually, we realized that Pinia already implements most of what we wanted in Vuex 5, and decided to make it the new recommendation instead. + +Compared to Vuex, Pinia provides a simpler API with less ceremony, offers Composition-API-style APIs, and most importantly, has solid type inference support when used with TypeScript. + +### RFCs + +Initially Pinia didn't go through any RFC process. I tested out ideas based on my experience developing applications, reading other people's code, working for clients who use Pinia, and answering questions on Discord. +This allowed me to provide a solution that works and is adapted to a variety of cases and application sizes. I used to publish often and made the library evolve while keeping its core API the same. + +Now that Pinia has become the default state management solution, it is subject to the same RFC process as other core libraries in the Vue ecosystem and its API has entered a stable state. + +### Comparison with Vuex 3.x/4.x + +> Vuex 3.x is Vuex for Vue 2 while Vuex 4.x is for Vue 3 + +Pinia API is very different from Vuex ≤4, namely: + +- _mutations_ no longer exist. They were often perceived as **_extremely_ verbose**. They initially brought devtools integration but that is no longer an issue. +- No need to create custom complex wrappers to support TypeScript, everything is typed and the API is designed in a way to leverage TS type inference as much as possible. +- No more magic strings to inject, import the functions, call them, enjoy autocompletion! +- No need to dynamically add stores, they are all dynamic by default and you won't even notice. Note you can still manually use a store to register it whenever you want but because it is automatic you don't need to worry about it. +- No more nested structuring of _modules_. You can still nest stores implicitly by importing and _using_ a store inside another but Pinia offers a flat structuring by design while still enabling ways of cross composition among stores. **You can even have circular dependencies of stores**. +- No _namespaced modules_. Given the flat architecture of stores, "namespacing" stores is inherent to how they are defined and you could say all stores are namespaced. + +For more detailed instructions on how to convert an existing Vuex ≤4 project to use Pinia, see the [Migration from Vuex Guide](./cookbook/migration-vuex.md). diff --git a/packages/docs-new/package.json b/packages/docs-new/package.json new file mode 100644 index 00000000..9a071250 --- /dev/null +++ b/packages/docs-new/package.json @@ -0,0 +1,16 @@ +{ + "name": "@pinia/docs-new", + "version": "0.0.0", + "private": true, + "scripts": { + "docs": "vitepress dev .", + "docs:api": "node run-typedoc.js", + "docs:build": "vitepress build ." + }, + "dependencies": { + "@chenfengyuan/vue-countdown": "^2.1.0", + "@vueuse/core": "^9.5.0", + "pinia": "workspace:^2.0.18", + "vitepress": "^1.0.0-0" + } +} diff --git a/packages/docs-new/public/dank-mono.css b/packages/docs-new/public/dank-mono.css new file mode 100644 index 00000000..4f42acfe --- /dev/null +++ b/packages/docs-new/public/dank-mono.css @@ -0,0 +1,97 @@ +/*! + * @preserve + * Dank Mono (v0.491) + * This font is subject to a EULA. A user-licence can be acquired at: + * https://dank.sh + * © 2018 Phil Plückthun. All Rights Reserved. + */ +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: normal; + unicode-range: U+0000-007F; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AACl4AAwAAAAAN9AAAH2yAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAABJAAAIOcAACdy/PH/FEdERUYAACT0AAAAGwAAABwAhgCtR1BPUwAAJRAAAAAgAAAAIER2THVHU1VCAAAlMAAABEgAAAooSL5Pxk9TLzIAACNYAAAAUAAAAGBrZsm/Y21hcAAAJGAAAAB8AAAArgoqCZNoZWFkAAAiDAAAADYAAAA2EgrRX2hoZWEAACM4AAAAHwAAACQJT/16aG10eAAAIkQAAADzAAACUGYhRd1tYXhwAAABHAAAAAYAAAAGAJ1QAG5hbWUAACOoAAAAtwAAAUgWczCucG9zdAAAJNwAAAAWAAAAIP+4ADMAAFAAAJ0AAHjaPEgzQygAEP7unm37Zdu2bdu2l2zbzW1xymv+A2nOtuvuM+Ehg4jeGfpFR1jFRMdI2QeFJEX6xV+NxgcuOHClAzc++Pqw9CX97kSOgsJ5Scl9ePnk9M1x2y/gidylPFx7dKkvqt8MXtrfjPeX+vLdO+gwCM/xAT8gACkoQwcmsIMHAhGFJGShAJVoQhe60Y9hTGERW/SYPpMkaZEjhVAqlVA79dIEzdES7fMz/szCLMGyrMTqrMOGbMbW7MBu7MNBHMFxnMJZnMNFXMH1PM8rYdKRMQGRjvYXZJO9bsMwDIRfRWO7GHb+MxYdu2TrEkCQbSYWIkspZadJn77lqSnkZtDH40E4kAPfvohD0frQB0+ow2dItWNKziGMnIS9JCfaa6p0IQ9F9tgNUN7+BiHZjz1LslRJRkWyKCSLQLIISUZFsigkQyFZxOhb4tgEpqL/gYkDsY0nHZ2JXeHs0dRsGnJ0GHRt+M850TDxmuCC1+D/PnPp2jjTa/oYjXswMru7nTvy+shkBmJYZ2Ib2uwPJtT3gdNghjXGZSyaWTLvxJxmuzFqAbp8mGn/6OYpjmLUtcmatAZ6eZVgJpgLFoKlYCVYCzaCLT6XYAXOwDm4AJfgClyDG3BblmWx2Fav4XzDwmr/1Oyf1aysNqq+qV1nndo53ZyGbvSFenFO4V9UTJH4Qm0h16rkXNX9XN/lyL7Fy+DB4M+wgeEywy9Gf8YExsmMMxivMjEyWTL5AzPEfqZDTIC+vAQwhuSLe9pMdY9uGhltmdHdbnGHMEHkJEIICUFukYg7RMhhhf3byxV7YdexIXHfQg5H3HHEkUTEESYYgr3sBn/yeryJ/XqC3e8mSVdXvXqv672qX/3q1ZMGdm1z7Sdas9ZLu0J7Q9dSN0E3XTdXt0d3Wfeb7i0hpDFxJ75kNkkmC8ly8i1ZTdaRTHKSVJIH5An5k7wkb+gGNEM3og10a7oj3ZXuS/vTI+kgehwdRcfRU+k0ein9Pf0jvZ7OpLPpXfRBOp8uosvoKvpX+jWNjJZpyDRlWrqwebnJ2XFx85PjOAF8/UX4vgN0Rx/064odcD1u+As7wGC5SocNxzyHNFhw8RUwGTI0ySjqhp8bceGYHshLnNBFbCOqj8OZWw+uyNWfiDowzC9m5oTYbVPzPpXGpcycNskYWhRfaTl/6HRBwv6xmyVknwq7dmXnHtqZPHuFtCJxftKktFUZLaN/Ttq+w1iKS4SxCZl7d+/dfTZTLkrbNzvBOGt6WojEK2VWexmDRrfeaMaB/Z6CUXpf1enG+D9+LTvzQuKsNCbasghyNE6qyyJW+hIkEviWxkXlwnOwkXX0K7QRXkCnbYGnKo33i4+/KT2cFnxaKjpDrkzqd7irCaUurtgEm74aCL0S5eefFs1c7qzvsHxoHDYwuQdvPz1VDgohY3b/PulPEwyovQU9QBpchF13yZwQLdqag1xuZ/ygc13zaFaAhQy0Gw2d0dnPe0ZYorSM+YbJ2PbT7jU/6dvYS62KC31y28HD100vLgzoJOONkSIXm52cxwmj/zNwnLsJ9cNBB5EQewNagba0JCxgk/zbDvLLHNTeaWNCMzLoi8txyUvsBYOhGTR7+HyTzAtnHpBtAcmrkk0Te3/pFSevW1Pww48STjGL6NeD/RY9wMJsflR77M/xek7wXhQyub+p7djrqgXnk89AV3Ipfvhmh5XfEjpfUUeMPm4o4XAc+msH6A+fPL8ODbJVG/DKbPe3ZSn+ZnyFX0OBGQtotdYbfiYQWokdaQiAvcRiT3NXGjE4AvcSHOgJehp98GfiruyxMHxRG+g1DDjpr10XrwFrhA5df0NnCT+ry7LCtaf0pk0ZGT/LZaj/EzuON+JVpa0Ap6Ar8/piSJduQeHIyHwPlhcC6KyVW77fJAWO7E+DBLNIEI0SziIx0M61qPoawwvf4i8MVCu+pNzeyw/uMPjE7ktcRCxVqom92mzLeszANftNoty01mUxfDuR3zWbZK3bsXqP6eihJZPiE5IC5XmTt9LX9s2Ljp31nwncSBoafknsvpywfR/5cf7MNdNMLv6jzXIXxrc40lp96tzVGyMKfMJS0hakSdlMyYFDJdk7ln6xUfLETcK46buPrZB3M9s27cs8ZDq9O2FizJyZIapD584gqdu2f3HY9LL0QrX8nCkfddLsPibcz/9cVFVO9pqde6QUZlhc/JCE5B/WLZYew2rh3P753vLs1JkL402hs7cfPbF9yzlVT9+Rg1DT8YrX48fXKl+AZsQ9V3mKLnvT559ukLElRpLPs5Zu32X85cCFsxLm6tIW/vDTIhkEiCSr5383Z5bRbcr4sRI/kL1gVVirVllvo4QobDUWW8a76/vRCTf8oF0EtNTXUfV+O42tn2FovlfunWoIPQmt9b/VZZlp/sHlqw8fjr5gNgcFu5mLRz6QufTbk1OU0lQOrlnhlJX6PZSFYS3UKDMwEhpfhkHvgy87gk/DZ7YRwr9RxjZwiuaX2DcK5rAdxXNlWMpU5FzOqzC9LOmPBhR9uraXebirdESyAs/ZdUTRMeYMUpdthSxmby4cwmLCpSXNTAld8n10y1M/7Fv702Z9/VINH8k6FqkVDlrhaytn8xI6IHMl4o/Htc6XD7WD5uB6MeEqtptGDDnfQC9iSPT9bjF2w2aFk0cT3unRMyiwcgKkViOFe3DDAPwE3WRcbVNHsLpuhPCmqBaiIDb4F2wqY1f7K5q/fPXsplMm7iV0hp84HFYJTVPOVSmNb36Xwp2rgTfPOWhGcYPZESz3nOnEcpAHGdhkDf6MaUQJYMzfk7odVkhjdmyFdfgd4bJSbOHlFOSoYaoaxfrhzLpwsy0cJ5czfH+PaaPGcosqlAmV3EoL/FwJey1cerjIKaUCd0cZe5eCphY4YNFyE9k4lstQlhHOV1RZnZUzQZ/X0AzGcPnQ+DAw+RRXbpueypXj37Zws2N9lPwfoMZZIf8Z9y16ctTLCSwHAyxgsFBctRVKrWpbFXcEuuQ+yaNg0nnwLtIq3hyss1DKFlgnWOwmd4ZXHgheExJHxchcirLcyg1g+CxflquIV3UN6/ka+3Ip1QUwK5VLgZpUStU2KL/4CKW4FGlhIyfgItowy3YVFjE8RpZEpyjjLUrzEifujq3DXeqxBe5XabkCyMynlN6q7d4cVRUrWrWcBWaWKw1Ur+RCXg43kIW9auVwloJc9Ulp2rXS+OqppRrNiKaaZg2pZQZNnMqqNRoPjcqg1T1e00Cl2hpNE5VjazRajWaaRtNao2mq0XhrqJ81mkiNJlqjGaTReGo0DSkNr6EyG6rcQENUNq7RSJRGpeUTNRpao2mr8naNJkqjEnJqp16TrqFynDSZKpFQLbCqsEll6301EzTTNTM1SZrdmhOaK5prmnLNfc1DajAVTEVQUdR0lZuvpzZSW6m91FHqKfWyAauy89gGixv81GCvSjlQ20jbUvuldpV2jfa0zqjrpuunG62L1S3RLdcd153XlZH+JIbsoGl6EP0D/TfTk9mvN+rb6l31A/Uj9GH6GP1U/Wz9Yv2X+gx9tn63vlB/RX9Df0//WF+rf9ewRcMRDeMaLmj4fcN8thXblp3KLmG3sZfZ2+xvrI0L5fK5G1xNo56NEhrNabSsUU3j9o19G89tvI+n+I58LP8pv7AJ06RLkyFNpjVZ0eREk7dNlzajmzVr1qvZpmblThqn3k4jnRY57Xe6b6ANrQxGg8nQ2iAaJINsaGNoa2hnaG/oYOho6GTobHA2dDF0NXQzdOf7s5SyvkVHsTebbrHJFur071plOfh3FVdalVdPKKsVrA+0sMWW5SfGsDDB2kPMSvmATNBIXU5fwDXBigVwCl5Z7f71+xHDp88T6xX9DvuEclzfleU92BRROVzSWUyvtLWopJQ0teu1UWyGvb8nPLEfZPxDp3XuO3THyRgpNJCE5NdMeW2Cjr/dAEpWjjH+LK8u4OxU6qna6+8WWKs09RL5leW28FSqVq063SJCHChOE1u4iulVtk7TRbif3lbkPcTPHtjaVlAqYLR4Cq2ea5WXikn431jbF/exLTjPl7CVypAY6LT1IbSHz4z4Kd4UYHxvHM/03RJTA2HGD3xO+ofPRcZ07T1f4j+4I5hVHrToJaYnigCKfyeRHyWOEZNE5XOIFzqI6i6Rq9zVKjtHsRhXZ8LxNhPpTcOX9awL1qgkTJU4alXaq6PZbsvqwA5ik0Ulx5bVnQ0WKfC29BSDxHqvwRXlQRt2Muv0yAq/WA1p8Hu8CAfeI14t89XqLzcs3ag3HJ2edzatOFScIcqe4qJ7SqCKIcng311UO5a+76jca2E42o81pMUwhqNDWS+WsjzSwmPlQTsWllVCZupzaOX20AmSrYZKZaAtUjhWmBS8TTb8emc/MVT+FuN52MfUqasXGmXMRMoTKHsQY/2KKOMhpRJTIJOxns27IPHDRKfTD+Cl1fCXkjZW9GMMtd1Yw1+erKHWnc1SIcyHVVyY/43Y8SsjWC3ctrkKbcQuogzJ9pfObH3dg3/qEhx1OOWh7VCAqLRR3Tb4YxN27ySMY+3LGPO42OCoiG0XkiSv6aRHQeXkW6YXz++AUc5QSokaNwpoq9ambYF3oSTQMZ8oyFMV5Zb4iFlTRO/bC1OdKqwgV6k/hlIlQUXegHmJwcny18zZBVMPhpgikhaEz5cNm06Z7aXP1F0I3jGG0hNbM7fuMRVsmTJRtvevUtozP65cu2KtZDhwaXt21vYPDb5VSg9V9sX8O53UCTSMVa6WOIv8iPp5yy6qUoIcti31caqEJ/HiKjVE29ftWbNOalP3Mx3CyobKGaLkKX4UvV4velRZMlYczBgqi2Mm7PAyYd+e7XAYjnmNzaDPlaLNOfkyrmKs3xNbiKrOn3Wxuwqj5oYlmk3tfZ6DqOoEQ8mDlxWXYwfnSKo3lGWpFKy+r1UuKondRD49RFQ0cK8Ly6c/VAZVUhCi+uo76C64smh87vViCivxg8QhojqzzrXoyRqOtlfn1lRxjIov/xkrood9OLoowx2zPtpe6pj1sx1RGCcqG5UHHVmH2MUWGAgFMAz2qVJownhsBfGODkNxHwZhAXlGQ1tIhDaYSHiVIVHKHKW0M6t+qW85pRwq6SOm37b1vUpdqoE/nmvhvi1HQBck6InLMRl06AW9oQ8Q8Ib5sBQZ8EYXeYSuIGfexF0yfmOfRRacXnL1phGml4H4IkOq66uLn5G5L1GGZcoskhmy1n+QEYP9sWVb6QP2wBkH+JTU8yMzEx/oH+2Wtsat5cM1pbt35+ntldHsSotNUNEu4aFWnT92AfvhCgzFzugvuSurLXaZgc7gDzMgCDyNtb1uYasOXhNdoqWLceR24fktJabHhXEBMp9+B86Xw6E7lDL/qRZa2yYIZ8JJ4ZbL6wtMf5wLNXcLHI9Enuh3hAb2+ug2rhPCnftN3HY9Ug6KI32PVcTUmsD1VwsYf4u6M2SPXJZz8MpDIzTv/BAFrz4JQYn1EDbHoiSrA7KpAyqE0wJSlbQ6Q7A9+tHwrZLsCMi39mTiDptxjhXmAKWCfJXS9A4Eqa5PfKgFP6Wl4EXvVVoTpbUfltLJ+A05nFm4pdQE7f7ogN3RrSNy2MbD52C5n1x0joScOJVUYqq5cAsMoA9ST09jQpOnJsuqVrDdhHmV1BMrvHygVRbbhgmnrpJtsSM3BJnQU9XRAkfJmMTgoD/bqeHs/d//git07/oH9vTwT4qdJp1UCdf958DlHvvPwl0SdqyjBech94CskCuYs/tuH75p+rPct2MHj0GdZf4DLez/ez0ttH/np5hpbINxOBpnLcNc/e90BuTCKJgFEsTp+fc75FMLlKsfVgr+wq9hf2CT/VLVgZPVK57qVRseZdjYhI3dA1Df87TZOlVGbtq4br2MqK/xBBHavrwJjSX8UtkvVObFDl0hr/CfFOwTrx94KOZCmbG8MPeOtKxOFtpNyLt17+TN2n0yNJhx0tPb6D02rr3Ep1fCtJuKtoqqcACqFoKqhdARJCnvyqKLJvAEDgQYJUMSA4O6vMY+2Lt9e3RF8/Nu0PNu2YG8A1IYg+0GdEMuLmrtxtkSdLTRwl83BiHJkAcz42Z4h/uauvjdBN2ru/dq1DhEsfBqgJg1S4SSc1ooaXEOakJobxUm54qwoQ+bHiYqvS9pYT6cE/qJvdQdeqZIwROVNkbBJ73F9AQR3Aew6XEiFLqJWbEs1ASw81WJCcdhcKFWaQLe7cX0cPZYf1EZHMXacgaISvhHW4qf8NGaMmauaAvtwyrDP9qzff3enhL+j0GluLeoDEgQlZoBrDI0TrSlu6naYllbVgCrDHaQ1n/tKu/ai4pfOKt83l+sVnpQ+Jnyp1ZpoEQJsbn281iXF0F4275U6m9K/o/mrhbWKuf+H0cd9ZzzPx9ybPdTqRvKeu0N2xFBiXznXXzYHvHWi/AboBmsDhBhsxLdTrQ31dWnf/hv31BK60LhY0Ip7bv0NUskbxVbVWljinJWXYu7XmuhAOYJlfR6+JvA+HYqF/ncfqCvg4CFlsOqCghRITAGNgl7YRWprvCkd0EIGUjPwRCCEr3zn5e+g+/QM3AVcfe7Qc/GUFKlCoYSkD687FRffi2nsX8LtZdDFcg0n+744sK+ovo9ik0F+29/AZNVC5W2PKFd7ykBM+VTgeTSvqod10wvT0wbiENw/oll+IP8C50BM2DOJIg3grHHRewo9a77sq9oy0rdnuoEQ978/saQCENsO8ayhsTfvx/L8h9atr0xxMA22w4BK5mi/xBD1uqNe6Hz2kd6C8Pvf1PvvweK/GF/X637x2WyN+twoVTv/QrV+8qj0SyPkeXUNgW0tqYYKZTbwY+pr7J1wRZCXRc/WxeaR//6w8941c/rnmiVRbZooYTBJmvbxiDxy43ub4xYHIwNJXcG2J9GRcnmuHk+Rmz8OhyarpVKmCFLhmbeuXD4aLWxPKnMY510a92uCzceTC4/Y7xRtq5iifRR/cwUCq4+0YKXqr5ibdniIeMn+/U3DtkVfGux5LE4yd/P9XB0sNFr6IIha6XhDDRd8voENL4Vt9VqPPZTMbCShcGGiy8Uyg9yj5YbgbQtwCYO5ZF3bVfvUTDlmRamjGLfTYP1d9HTdhU87zH/NOIzrTJeZZvrB4Fn3VX09ID19aN+O1sd8ugaLeTCMQF8TkEjkMBZCj1N0B1dhqAzBhsx+AY6g/oqnVKr1eZQ4MDX+LG70rBGqzRUu6NvKHIoobN0agIBd3C5Ac4QbITgIeCM6qsUqlarzaewEfoY+cmOebAt9U6q09/UenVxGRJb674czeKtweIcUaGfwKRnhvS33zlCYQ9Ug9FvkiMdMJTxXxbfxhEJzarJ/jL6MNjMGg1NJSWQNpwowUzbE8al7olgpoeEZ96RC1dcumm0Jp15H5Uj1usZ1wuNdwozKyQr7XDOmJS3re6tVKec/RksemaYpdoLFgyR9+qc302zOTOGn+/icsZxSFaLLnVXZ4vbPqzfS8q0dipZaVG/fu0b7U0db0Hsh2ZFrm8++L5ZyXn/fHfVIfZvbRAbI9oSFL2gTIhk7a70Y3ALPNQGo4j9eCCr/Ej7o4HYR6pFZ7o9hl5LUIOTQpSLqmw2zd+3/Gl5bqFaM91gnfC9hdTtos8+J7ZZkWydH10CbOChvuhC3vUOZN9y9LiupK4skLXtosd1IXWz1JIf7YOtriU8Azfytk8k+46jz/1JeNhsoWzFKjUJ9oPDGEHedXB/24HGwxhZ7K/oYSN5xvAwwPI5dPjC4mRo8/aS+vEW+0Pme3eCB0cKBu+IbUmFx7ZvKzw2d1tUVGJSlGTIsr3GDqrQXeYrd8L/E3Jtan3IlT9Gs7bowSK/qNLWpj7pQKzwSZVWuQXTBDANLcL2uyU4wJz4+kByQUTRmZZTz/ofHLRWf9wcwSCz0GMANjC2ve0D7BQJzzOBP0Rtm3UkKKTljgn34558pQ+0XmOgxcb7N8FotPdUGgmleQUV6yVcw3jtnHLkkhHu25sLy/tFTBr4mR5yGOuCwunLe+l5yLao3N+iVbbASwH7gIs697GXO/RCFwu4QB+ajxGVbEXvcLu9rRqfthj3sczvVq5SKkqatUouoGAX4PMZ7lCNMViNkw56KE7wOXkv01CVgYaqjAujOOHnB+9iNUyCaoiZYSE8pFg2qR/xmdJFGB4y0TNevhBDinbv23mgZIdzjen+6bIz2+WgfBIxd/q0+BFz/+pg4hefh+GnYYHjL3WoAjqXr3f80UKA8o3gLiru8J0QeMieXLeueDK5qNwW3Fgost9Um87r3FgHz7LchO8rqSvWygcQo2L7rw6+HO+PgXYdTs1Fp7s+8qYFmxfuWFR2puWCNV+unrld73tpygsYZlQan4PGIAJ9wQ0HSKOZ3BkJG2NMqPVzxTbofKddbe6+Dfv3yNOtygwGJvWFxugpVdfNFrwmXgHqRd7ZyrzsRZO3yJjGzJiXtHiaif/stiMBdbBaa+P3CLHH4/OnH87OySrYVDiuuOWCsLTolCl63AIX0ndcNF48dqbo9KnESTul0yfJ1emB+9xM2NDHF5kMefKGyVfNUtdRkYPNRmwGLDLQvOJazvXz0sPuBHdcFQYOtFgv7j99/bhcGHTNq7qTfuzEiFlhJn4S60DRZ2+nMbxSnUIpsWPFpzQGwTpiptEb15GnNDhBb8KrMHX5GUx6osJUgDJMgKEqZvfLxyaSPXA4ZKpwVPcEMxlD+nAlUG2xHoVmMvgwZasOg0bF8jbLDpXJrlgo+K4YHWE0nPAKXzBEMtueWOmKwgVeckTG0KFG866J7/eG8WbjP3CVleKk5NfDVeTbkFFsnbOaxRsEy1WEUpFdfUR61COXy93ZogOip6kI/+sz7dvJ8Itgz1c4UuKiju4Wo+QrLLHn41bBxjNKKN4g7yf+M4u2tS7x/cx3dUdX+/z6x/sF4GoBV2V+/eP9OoBW6jp4u1dFkL30273qOni3V0Wcvf/gE0jKcKE1nR3JtrZNpNUC+glqKZBVi/T3Xf4t89DfAs1V+ClRiWi8O7TsQeo2ub/l6WcBriPJu2butmwaTV3zHEtouwOHHGNblEK9bayOrfEodjhjz7dzxEUdl50lPo7w2XPtCwUlFG6QOp6px7bWuiyoEd51oOEwRASX2znc6Oihx43FZXgYIomKdryyxNYVZlAQAju1EGLL78vy/5d/6RuU0o0wfuORjTTO3MBIWW5/N2q4uhG7shFXzlq48kaNLI0a25yb1wpHDBp/R1YyQrNW85iiqNFqxjGLekj91cC9QXqDggYVDe41qNY21jbTztCu1x7XXtL+rn2ha6DjdP66EbqJum90ebqzuiu6R7rXur8JISxpSnqSfmQg8SbhZCKZTKaTBPIpWUK+IivJZrKf5JFj5CQpIpdIJXlEnpE3xEb+pnW0Ey3RneietCs9QBgtOkjbUPFFuTdq5C+ZjJTUjDQTLkAmCOm2NWPBSbZkXN+366R+7uJZny+WauEeDd1RAxrsJqXZtwn+Y/dcLLfsBJ/N2StXZMu9mBFRg2b5m3gBXF8DDyPBvz3osS/264QEA3DUK2wErvJNXadR1dAR2hdXvciQX2dcdsdORuwc2K+dxAvo1q8NjvxaDmWwm2pHA90e3D1QXCSFXiaBI8alhZo6+d2ulZWuq1adIBD4g9sbNJvAOYzF6zQ0/O7ITWhgAu1pf+S/kvmh6oV+oKlHwOM/ZKhywUuqympsDnxF2aGLeyROwP4p+CgOO0hT/jsRSNp/9f3olPZxSCa310+BDunwCPobzfQ3GLffbjiOY/RVdP6bU9A6+83mtoXYOretWnEGxhxXDBBn5FVtiSi/1zb3wacOZWntE/q/15UC8kdduRj8UdeBRw5Ve/t91HQGgh2avtq8cmu2kRMWmkfh0AD09Ie+g2Dgp7V6NzoLPwGCcypUBRn0tH7zhsfNGP1Tq0XDXy24nnv4TavfIAEYuLQHDHrHzd1gZryIEWYRvV1EARtkhW0/Zjx9Jefx5fy0sOPS2bPk0iSvfd1M2MrVv1OfsgkwYIb8V+ruBfHGMf7hHv6jc66FSGOCyaCLd2NfmkB6cqf2UfBR7LBD5vxgBIMtl/n0R9qE3EjQQAzMuQ2NQLyQkzykWL5xgVQN9N/vYcLR2AjdcA1+W4NtwQ/c3oAe/GXuQmzITm8TunbshL2xXy3qoOf54i35Z2Rs9P9L/GYwatAS0IYxYHOkgb6xRxKstEIlA5uUSML7KUMddz+dWCXDbO/McGEpa3fPlWckkfjtB9Mumx5eO3Axb9b+yevkQxu37j1gvOB/cFh01MK56RKXm53NrXTksCE1mFWGKCOFc9u3HLtpBOPAYuwv4eC6LPiMBufnv0GHmojy3ntldIfVAtAnE9wHRk9FTu4NqTR0tdcK2BVOQRsamIvh3boEhbSTecEPTX4DB0kxsL8A45iqe9Cw9Jbx+Ohc3zETZsfNk/IXkP3b9/540FS4/dPZMh+cFJEUZQoel2eVVU1LK3EpTHGkc3FWL89f8JTqA1iO8u7TuADMhBvC1rflMf/PHJ/jrisrlQtN2X6tZPPW02ezkgICUuaOc9RDYrmyOJUTJtCZ4Edg5kQ005/1JBt/3LYxx1R2fpzX4KETvGJiN+6Jl3fvIfN27FySYzp7YGtRceKO0FlzFi38TObNLMX9925VzfssIQ5iRs8cMdXbpGavld9HsX1p9WNK0BtKiAsNM+1tcK69OfmFBh8ogv5YRH6ncZryiSP5Cr+WK3QqdS9WfKZVLC2GsGY7jVUKbXZcIfwKVfgrw/uzuME+VHAeZK2VufRKGHkbxtzmUsZM9fticeG8ltt/vvty0yX9N2tWrfnRyFmpvyawSnGkCLs/3P9xDkdQk0StspBLv6lEl3OdVOrJldbA/ecc9isV6vOe/yY9ZVzKvE+I8tkrt36/WeKmiBNFTs31NS/nBNwZDVPoYS4dzCvQSX+ZzgCnWmvJMz1P+bFaDqPHsRytEl7OliVwtiMO6hXxzkulXtx78s9bx7NcVv3F7sdDbs6/h1xOTcvCUmZKflHqlVCRG8k4bs056mkoy0WJWs4KpVWqjUyBy3tSAO2PUzC+CDzPa2EZV678GSDCkSqOVvkolwe5ORT4F0PARS3c59DHRVRtPkjlVk5Q1XwkBznPtJDDpcC5oxQsuKxV4jnqnFVpYtVyi6qiUpTVtzlquCp+q/LwbwXc/wA4RR1rAAABAAAAAH2y3+sOvl8PPPUAAwSwAAAAANcfBxAAAAAA1x8HEP2F/vcHMQTuAAAAAwACAAAAAAAAeNqMy4FGQ1Ecx/HbAoJAhAAIAl3GpJB0sep2s2mHVJZEjFYpiGBKAdhLFEQQKSxRCBrsLXqF+uAgwPDxPY7fv9RNCioEUmq0OCdljTYbBA6psEmZ7dhAQZV1pplhnikWyKmTsUjOCSlNzqIGbY7J2P+3X2KVvajOCmWa1DhijhY5WwR2aFAlkPFIn3c+6PHJF6+80Rt+9zuhpyyXuiNjOojNY3d58B7XARmTJNxxzzcvPPPEFT9uRjlgllt/HW64pk9BEtvhkou/gdwD9bMqNB6UgfqlIXHJqA3FwUD+NiQze4Bi4WD9EQz7gTRRAAAIEYwgAHjaY2BkYGCZ/u87AwPTlL+tv5PZDYEiqKAbAKGeBswAeNpjYGaaxDiBgZWBg5mH6QIDA0MUhGZcwmDEGMYABKycDDDAyIAEQr3D/RgcGBQY6piX//vO8IFlOiNnAgPDbJAckxUzD5BSYGABAC5IDFB42lTMtUFrYQAF4O89fIs7wY97gwyAaxV391SZhxlx4qmOH6x5teTf8gZqjPk/y2pj/t9/rTFfQmfMl007VvSm/uqwf6mqpq8hLyunJfImsmPLtiORhL7ItZy80pCVfEgqaslpqwgi50rDdPqjOVRpTWkNHWkpQTAw8Dj0mvKqKiJbgj3Htp26djd0thbcnKqW5LDfEdkWhvmRI6fK4orSw05GUJKXsCPYFxzY8zNzIDQFAPnwKz4AeNoMyDVhQwEQBuCvuBYF1EBhK5OHYphpDuMcC3ESjrIHdz9jHwe4jHVP7A7NcO7UgX23Hn1IyKnrG4chrtx58ikpr2EQbxwT7ggX4RJ7znGDB/CGpn9pCS0DIxlVNd/a/tQl5fzK6unoShkq+1ExkTdVNFYSrQoAnAsdtXjaY2BmAIP/WxmMgBQjAxoAACzTAesAAHjaY2BkYGDgYYAAJiBkZDAF4lIgZAQABxsBNAAAAQAAAAoAHAAeAAFERkxUAAgABAAAAAD//wAAAAAAAHjajZUDkGVZE4S/g9uvbVs/1rbGXo9ttsa2bdv2BNa2EVjbtnG74sTr+8ZxorK78mRl5TMKiGWP2odu1KRVaxIregyrohgL8M8/8jdEIrmU4tXv2LiYixve0bqYei3rdyzmttb1/f+7t76jZTE1Tq+IJok86Tw0MSSTQb7rDbGkkEmB6y1xpJJFoes94kkjmyLXR5FAOjkUU9KrR8Uwnjwb7F1VXcnzZ4N9h/ToxatngxXVvSp4W/BDwc8FvxX8uWp45RD+PBsEFLGCUYAhRDQx0ltBI4igEtSCHgVcHz4V3EZXH6fxbNSuqA9DUaHy0NWhO0I1oSWhbaH7Q8+GXg99GvozOjO6PPrq6LbOKVq2KjllAa6U2LrOe/OfHyP06WgUV3ITSGZNLKUYFKkoLnd548S1dZjPx/EyXY/mIP+14E7nU48ZrOIQT/Iuv6pkVarqqfaqRk1Rm9QR9bR6XX2uftfx+lx9rW6hO+sxeo5eo/foh/Xr+kv9t0k25eZq097UmFlmjdln7jcvmg/Nrzbbnmsb2Na2rx1l59gN9oh92X5o//QSvf96V3pNvK7eOG+Bt897FAMo72oU17usLj3nRjyqWaKUG/Wo8PHCD8A4dh8E+MF1em9GgO+FcWxVhH6QS9JWNisgAackGxBGlJMEewf8/xu47RP2T3RTicJXIHr7c9g/6QT/JFFOFuzr9I+jeFVuk8P6hwFhRDnFJemI9qsjiK4FrqTXXOsf5Rfg9Oe6cs4RSVLEeargCPfMgPZwfsp+KyWOLum7Yb+UU/jNEBzl9jdA20P2kMt7KcpukpK8dhGlKB9x/uJnJwXcOmEc299tSRV+iNMvwtjbao/bMAFlb0TbG31EdpxPBcpHv6DuWbS5Ntf5GRQXomwUUkAaBk06vsJ8D45TKJYJ1tSlNS8HbjtgHHu3m0oXvhrRmy0Ys6X2IGnNIiltFvkoac0EMwHlCgzGVNQelF+A8/Hzm44+KiADl980QQrIDOS/FBynUKwQnO58UpECssLJcfpsUU5zynKMKa89gAlPapPqI5IcA8qVJNdf1x6UX+Hk+k20ftNH2YBLrp9ECsipS66PgeMUilWCVc5nGVLgK11yPcXp80RZiSgZg6Kz452SFhHKoU55KZrbfEQy1KMU5ZfL7iYvlrl8mWsT/AQE+IGC3U/4BOafQt9TsEdgy92B29HhqS0RUzPDvx2aQuKpR30a0JBGNKYJTWlGc+YwC41xvwUD/BPLaP/EBWYTsQxiMHOZh8LSy1eNYkJAEY9hPhXynWjoTZ/ArSLKfUMFJ2L9WijfcZq+J3hNZRFTxGuE2xTMMoTFzGCJZOnkK0YyKqDI96uGpcftqmaZ7OrAyECyiykMdKUkBrp0ioKPwX2vjzzOt4qV4tsuwjed+sFZ98lqd9yjXMNqhqIwtPHvugduk/EYyFp6MpN1KDxf0Z0ejGbCv+mdGSk=) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: normal; + unicode-range: U+0080-1AFF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: normal; + unicode-range: U+1B00-218F; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: normal; + unicode-range: U+2190-21FF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: normal; + unicode-range: U+2200-10FFFF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: italic; + unicode-range: U+0000-007F; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: italic; + unicode-range: U+0080-1AFF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: italic; + unicode-range: U+1B00-218F; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: italic; + unicode-range: U+2190-21FF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} +@font-face { + font-family: 'dm'; + font-weight: normal; + font-style: italic; + unicode-range: U+2200-10FFFF; + src: local('☺'), + url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); +} diff --git a/packages/docs-new/public/logo.png b/packages/docs-new/public/logo.png new file mode 100644 index 00000000..e9020207 Binary files /dev/null and b/packages/docs-new/public/logo.png differ diff --git a/packages/docs-new/public/logo.svg b/packages/docs-new/public/logo.svg new file mode 100644 index 00000000..3a20cba1 --- /dev/null +++ b/packages/docs-new/public/logo.svg @@ -0,0 +1 @@ + diff --git a/packages/docs-new/public/social.png b/packages/docs-new/public/social.png new file mode 100644 index 00000000..3b1e9579 Binary files /dev/null and b/packages/docs-new/public/social.png differ diff --git a/packages/docs-new/public/sponsors/fincliplogo_black_svg.svg b/packages/docs-new/public/sponsors/fincliplogo_black_svg.svg new file mode 100644 index 00000000..848f93d5 --- /dev/null +++ b/packages/docs-new/public/sponsors/fincliplogo_black_svg.svg @@ -0,0 +1 @@ +五行代码,让你的APP拥有小程序运行能力 \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/fincliplogo_white_svg.svg b/packages/docs-new/public/sponsors/fincliplogo_white_svg.svg new file mode 100644 index 00000000..3254e538 --- /dev/null +++ b/packages/docs-new/public/sponsors/fincliplogo_white_svg.svg @@ -0,0 +1 @@ +五行代码,让你的APP拥有小程序运行能力 \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/logo.svg b/packages/docs-new/public/sponsors/logo.svg new file mode 100644 index 00000000..19e8b2b0 --- /dev/null +++ b/packages/docs-new/public/sponsors/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/passionate-people-dark.svg b/packages/docs-new/public/sponsors/passionate-people-dark.svg new file mode 100644 index 00000000..164f332b --- /dev/null +++ b/packages/docs-new/public/sponsors/passionate-people-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/passionate-people-light.svg b/packages/docs-new/public/sponsors/passionate-people-light.svg new file mode 100644 index 00000000..a0215ca5 --- /dev/null +++ b/packages/docs-new/public/sponsors/passionate-people-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/vuejobs.svg b/packages/docs-new/public/sponsors/vuejobs.svg new file mode 100644 index 00000000..5504b855 --- /dev/null +++ b/packages/docs-new/public/sponsors/vuejobs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/vuetify-logo-dark-text.svg b/packages/docs-new/public/sponsors/vuetify-logo-dark-text.svg new file mode 100644 index 00000000..6da9a386 --- /dev/null +++ b/packages/docs-new/public/sponsors/vuetify-logo-dark-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/public/sponsors/vuetify-logo-light-text.svg b/packages/docs-new/public/sponsors/vuetify-logo-light-text.svg new file mode 100644 index 00000000..c1d4cce0 --- /dev/null +++ b/packages/docs-new/public/sponsors/vuetify-logo-light-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/docs-new/ssr/index.md b/packages/docs-new/ssr/index.md new file mode 100644 index 00000000..85afe4b1 --- /dev/null +++ b/packages/docs-new/ssr/index.md @@ -0,0 +1,111 @@ +# Server Side Rendering (SSR) + +:::tip +If you are using **Nuxt.js,** you need to read [**these instructions**](./nuxt.md) instead. +::: + +Creating stores with Pinia should work out of the box for SSR as long as you call your `useStore()` functions at the top of `setup` functions, `getters` and `actions`: + +```js +export default defineComponent({ + setup() { + // this works because pinia knows what application is running inside of + // `setup()` + const main = useMainStore() + return { main } + }, +}) +``` + +## Using the store outside of `setup()` + +If you need to use the store somewhere else, you need to pass the `pinia` instance [that was passed to the app](#install-the-plugin) to the `useStore()` function call: + +```js +const pinia = createPinia() +const app = createApp(App) + +app.use(router) +app.use(pinia) + +router.beforeEach((to) => { + // ✅ This will work make sure the correct store is used for the + // current running app + const main = useMainStore(pinia) + + if (to.meta.requiresAuth && !main.isLoggedIn) return '/login' +}) +``` + +Pinia conveniently adds itself as `$pinia` to your app so you can use it in functions like `serverPrefetch()`: + +```js +export default { + serverPrefetch() { + const store = useStore(this.$pinia) + }, +} +``` + +## State hydration + +To hydrate the initial state, you need to make sure the rootState is included somewhere in the HTML for Pinia to pick it up later on. Depending on what you are using for SSR, **you should escape the state for security reasons**. We recommend using [@nuxt/devalue](https://github.com/nuxt-contrib/devalue) which is the one used by Nuxt.js: + +```js +import devalue from '@nuxt/devalue' +import { createPinia } from 'pinia' +// retrieve the rootState server side +const pinia = createPinia() +const app = createApp(App) +app.use(router) +app.use(pinia) + +// after rendering the page, the root state is built and can be read directly +// on `pinia.state.value`. + +// serialize, escape (VERY important if the content of the state can be changed +// by the user, which is almost always the case), and place it somewhere on +// the page, for example, as a global variable. +devalue(pinia.state.value) +``` + +Depending on what you are using for SSR, you will set an _initial state_ variable that will be serialized in the HTML. You should also protect yourself from XSS attacks. For example, with [vite-ssr](https://github.com/frandiox/vite-ssr) you can use the [`transformState` option](https://github.com/frandiox/vite-ssr#state-serialization) and `@nuxt/devalue`: + +```js +import devalue from '@nuxt/devalue' + +export default viteSSR( + App, + { + routes, + transformState(state) { + return import.meta.env.SSR ? devalue(state) : state + }, + }, + ({ initialState }) => { + // ... + if (import.meta.env.SSR) { + // this will be stringified and set to window.__INITIAL_STATE__ + initialState.pinia = pinia.state.value + } else { + // on the client side, we restore the state + pinia.state.value = initialState.pinia + } + } +) +``` + +You can use [other alternatives](https://github.com/nuxt-contrib/devalue#see-also) to `@nuxt/devalue` depending on what you need, e.g. if you can serialize and parse your state with `JSON.stringify()`/`JSON.parse()`, **you could improve your performance by a lot**. + +Adapt this strategy to your environment. Make sure to hydrate pinia's state before calling any `useStore()` function on client side. For example, if we serialize the state into a ` +``` + +## 访问其他 getter %{#accessing-other-getters}% + +与计算属性一样,你也可以组合多个 getter。通过 `this`,你可以访问到其他任何 getter。即使你没有使用 TypeScript,你也可以用 [JSDoc](https://jsdoc.app/tags-returns.html) 来让你的 IDE 提示类型。 + +```js +export const useStore = defineStore('main', { + state: () => ({ + count: 0, + }), + getters: { + // 类型是自动推断出来的,因为我们没有使用 `this` + doubleCount: (state) => state.count * 2, + // 这里我们需要自己添加类型(在 JS 中使用 JSDoc) + // 可以用 this 来引用 getter + /** + * 返回 count 的值乘以 2 加 1 + * + * @returns {number} + */ + doubleCountPlusOne() { + // 自动补全 ✨ + return this.doubleCount + 1 + }, + }, +}) +``` + +## 向 getter 传递参数 %{#passing-arguments-to-getters}% + +*Getter* 只是幕后的**计算**属性,所以不可以向它们传递任何参数。不过,你可以从 *getter* 返回一个函数,该函数可以接受任意参数: + +```js +export const useStore = defineStore('main', { + getters: { + getUserById: (state) => { + return (userId) => state.users.find((user) => user.id === userId) + }, + }, +}) +``` + +并在组件中使用: + +```vue + + + +``` + +请注意,当你这样做时,**getter 将不再被缓存**,它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好: + +```js +export const useStore = defineStore('main', { + getters: { + getActiveUserById(state) { + const activeUsers = state.users.filter((user) => user.active) + return (userId) => activeUsers.find((user) => user.id === userId) + }, + }, +}) +``` + +## 访问其他 store 的 getter %{#accessing-other-stores-getters}% + +想要使用另一个 store 的 getter 的话,那就直接在 *getter* 内使用就好: + +```js +import { useOtherStore } from './other-store' + +export const useStore = defineStore('main', { + state: () => ({ + // ... + }), + getters: { + otherGetter(state) { + const otherStore = useOtherStore() + return state.localData + otherStore.data + }, + }, +}) +``` + +## 使用 `setup()` 时的用法 %{#usage-with-setup}% + +作为 store 的一个属性,你可以直接访问任何 getter(与 state 属性完全一样): + +```js +export default { + setup() { + const store = useStore() + + store.count = 3 + store.doubleCount // 6 + }, +} +``` + +## 使用选项式 API 的用法 %{#usage-with-the-options-api}% + + + +在下面的例子中,你可以假设相关的 store 已经创建了: + +```js +// 示例文件路径: +// ./src/stores/counter.js + +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), + getters: { + doubleCount(state) { + return state.count * 2 + }, + }, +}) +``` + +### 使用 `setup()` %{#with-setup}% + +虽然并不是每个开发者都会使用组合式 API,但 `setup()` 钩子依旧可以使 Pinia 在选项式 API 中更易用。并且不需要额外的映射辅助函数! + +```js +import { useCounterStore } from '../stores/counter' + +export default { + setup() { + const counterStore = useCounterStore() + + return { counterStore } + }, + computed: { + quadrupleCounter() { + return this.counterStore.doubleCount * 2 + }, + }, +} +``` + +### 不使用 `setup()` %{#without-setup}% + +你可以使用[前一节的 state](./state.md#options-api) 中的 `mapState()` 函数来将其映射为 getters: + +```js +import { mapState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // 允许在组件中访问 this.doubleCount + // 与从 store.doubleCount 中读取的相同 + ...mapState(useCounterStore, ['doubleCount']), + // 与上述相同,但将其注册为 this.myOwnName + ...mapState(useCounterStore, { + myOwnName: 'doubleCount', + // 你也可以写一个函数来获得对 store 的访问权 + double: store => store.doubleCount, + }), + }, +} +``` diff --git a/packages/docs-new/zh/core-concepts/index.md b/packages/docs-new/zh/core-concepts/index.md new file mode 100644 index 00000000..6ee26c46 --- /dev/null +++ b/packages/docs-new/zh/core-concepts/index.md @@ -0,0 +1,145 @@ +# 定义 Store %{#defining-a-store}% + + + +在深入研究核心概念之前,我们得知道 Store 是用 `defineStore()` 定义的,它的第一个参数要求是一个**独一无二的**名字: + +```js +import { defineStore } from 'pinia' + +// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`) +// 第一个参数是你的应用中 Store 的唯一 ID。 +export const useStore = defineStore('main', { + // 其他配置... +}) +``` + +这个**名字** ,也被用作 *id* ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 *use...* 是一个符合组合式函数风格的约定。 + +`defineStore()` 的第二个参数可接受两类值:Setup 函数或 Option 对象。 + +## Option Store %{#option-stores}% + +与 Vue 的选项式 API 类似,我们也可以传入一个带有 `state`、`actions` 与 `getters` 属性的 Option 对象 + +```js {2-10} +export const useCounterStore = defineStore('counter', { + state: () => ({ count: 0 }), + getters: { + double: (state) => state.count * 2, + }, + actions: { + increment() { + this.count++ + }, + }, +}) +``` + +你可以认为 `state` 是 store 的数据 (`data`),`getters` 是 store 的计算属性 (`computed`),而 `actions` 则是方法 (`methods`)。 + +为方便上手使用,Option Store 应尽可能直观简单。 + +## Setup Store %{#setup-stores}% + +也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 [setup 函数](https://cn.vuejs.org/api/composition-api-setup.html) 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。 + +```js +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + function increment() { + count.value++ + } + + return { count, increment } +}) +``` + +在 *Setup Store* 中: + +- `ref()` 就是 `state` 属性 +- `computed()` 就是 `getters` +- `function()` 就是 `actions` + +Setup store 比 [Option Store](#option-stores) 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何[组合式函数](https://cn.vuejs.org/guide/reusability/composables.html#composables)。不过,请记住,使用组合式函数会让 [SSR](../cookbook/composables.md) 变得更加复杂。 + +## 你应该选用哪种语法? %{#what-syntax-should-i-pick}% + +和[在 Vue 中如何选择组合式 API 与选项式 API](https://cn.vuejs.org/guide/introduction.html#which-to-choose) 一样,选择你觉得最舒服的那一个就好。如果你还不确定,可以先试试 [Option Store](#option-stores)。 + +## 使用 Store %{#using-the-store}% + +虽然我们前面定义了一个 store,但在 `setup()` 调用 `useStore()` 之前,store 实例是不会被创建的: + +```js +import { useCounterStore } from '@/stores/counter' + +export default { + setup() { + const store = useCounterStore() + + return { + // 为了能在模板中使用它,你可以返回整个 Store 实例。 + store, + } + }, +} +``` + +你可以定义任意多的 store,但为了让使用 pinia 的益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),**你应该在不同的文件中去定义 store**。 + +如果你还不会使用 `setup` 组件,[你也可以通过**映射辅助函数**来使用 Pinia](../cookbook/options-api.md)。 + +一旦 store 被实例化,你可以直接访问在 store 的 `state`、`getters` 和 `actions` 中定义的任何属性。我们将在后续章节继续了解这些细节,目前自动补全将帮助你使用相关属性。 + +请注意,`store` 是一个用 `reactive` 包装的对象,这意味着不需要在 getters 后面写 `.value`,就像 `setup` 中的 `props` 一样,**如果你写了,我们也不能解构它**: + +```js +export default defineComponent({ + setup() { + const store = useCounterStore() + // ❌ 这将无法生效,因为它破坏了响应性 + // 这与从 `props` 中解构是一样的。 + const { name, doubleCount } = store + + name // "eduardo" + doubleCount // 2 + + return { + // 始终是 "eduardo" + name, + // 始终是 2 + doubleCount, + // 这个将是响应式的 + doubleValue: computed(() => store.doubleCount), + } + }, +}) +``` + +为了从 store 中提取属性时保持其响应性,你需要使用 `storeToRefs()`。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上: + +```js +import { storeToRefs } from 'pinia' + +export default defineComponent({ + setup() { + const store = useCounterStore() + // `name` and `doubleCount` 都是响应式 refs + // 这也将为由插件添加的属性创建 refs + // 同时会跳过任何 action 或非响应式(非 ref/响应式)属性 + const { name, doubleCount } = storeToRefs(store) + // 名为 increment 的 action 可以直接提取 + const { increment } = store + + return { + name, + doubleCount, + increment, + } + }, +}) +``` diff --git a/packages/docs-new/zh/core-concepts/outside-component-usage.md b/packages/docs-new/zh/core-concepts/outside-component-usage.md new file mode 100644 index 00000000..af30f9f0 --- /dev/null +++ b/packages/docs-new/zh/core-concepts/outside-component-usage.md @@ -0,0 +1,59 @@ +# 在组件外使用 store %{#using-a-store-outside-of-a-component}% + +Pinia store 依靠 `pinia` 实例在所有调用中共享同一个 store 实例。大多数时候,只需调用你定义的 `useStore()` 函数,完全开箱即用。例如,在 `setup()` 中,你不需要再做任何事情。但在组件之外,情况就有点不同了。 +实际上,`useStore()` 给你的 `app` 自动注入了 `pinia` 实例。这意味着,如果 `pinia` 实例不能自动注入,你必须手动提供给 `useStore()` 函数。 +你可以根据不同的应用,以不同的方式解决这个问题。 + +## 单页面应用 %{#single-page-applications}% + +如果你不做任何 SSR(服务器端渲染),在用 `app.use(pinia)` 安装 pinia 插件后,对 `useStore()` 的任何调用都会正常执行: + +```js +import { useUserStore } from '@/stores/user' +import { createApp } from 'vue' +import App from './App.vue' + +// ❌ 失败,因为它是在创建 pinia 之前被调用的 +const userStore = useUserStore() + +const pinia = createPinia() +const app = createApp(App) +app.use(pinia) + +// ✅ 成功,因为 pinia 实例现在激活了 +const userStore = useUserStore() +``` + +为确保 pinia 实例被激活,最简单的方法就是将 `useStore()` 的调用放在 pinia 安装后才会执行的函数中。 + +让我们来看看这个在 Vue Router 的导航守卫中使用 store 的例子。 + +```js +import { createRouter } from 'vue-router' +const router = createRouter({ + // ... +}) + +// ❌ 由于引入顺序的问题,这将失败 +const store = useStore() + +router.beforeEach((to, from, next) => { + // 我们想用这里的 store + if (store.isLoggedIn) next() + else next('/login') +}) + +router.beforeEach((to) => { + // ✅ 这样做是可行的,因为路由器在安装完之后就会开始导航。 + // Pinia 也将被安装。 + const store = useStore() + + if (to.meta.requiresAuth && !store.isLoggedIn) return '/login' +}) +``` + +## 服务端渲染应用 %{#ssr-apps}% + +当处理服务端渲染时,你将必须把 `pinia` 实例传递给 `useStore()`。这可以防止 pinia 在不同的应用实例之间共享全局状态。 + +在[SSR 指南](/ssr/index.md)中有一整节专门讨论这个问题,这里只是一个简短的解释。 diff --git a/packages/docs-new/zh/core-concepts/plugins.md b/packages/docs-new/zh/core-concepts/plugins.md new file mode 100644 index 00000000..bb783af3 --- /dev/null +++ b/packages/docs-new/zh/core-concepts/plugins.md @@ -0,0 +1,390 @@ +# Plugins %{#plugins}% + +由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是你可以扩展的内容: + +- 为 store 添加新的属性 +- 定义 store 时增加新的选项 +- 为 store 增加新的方法 +- 包装现有的方法 +- 改变甚至取消 action +- 实现副作用,如[本地存储](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) +- **仅**应用插件于特定 store + +插件是通过 `pinia.use()` 添加到 pinia 实例的。最简单的例子是通过返回一个对象将一个静态属性添加到所有 store。 + +```js +import { createPinia } from 'pinia' + +// 在安装此插件后创建的每个 store 中都会添加一个名为 `secret` 的属性。 +// 插件可以保存在不同的文件中 +function SecretPiniaPlugin() { + return { secret: 'the cake is a lie' } +} + +const pinia = createPinia() +// 将该插件交给 Pinia +pinia.use(SecretPiniaPlugin) + +// 在另一个文件中 +const store = useStore() +store.secret // 'the cake is a lie' +``` + +这对添加全局对象很有用,如路由器、modal 或 toast 管理器。 + +## 简介 %{#introduction}% + +Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 *context*。 + +```js +export function myPiniaPlugin(context) { + context.pinia // 用 `createPinia()` 创建的 pinia。 + context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。 + context.store // 该插件想扩展的 store + context.options // 定义传给 `defineStore()` 的 store 的可选对象。 + // ... +} +``` + +然后用 `pinia.use()` 将这个函数传给 `pinia`: + +```js +pinia.use(myPiniaPlugin) +``` + +插件只会应用于**在 `pinia` 传递给应用后**创建的 store,否则它们不会生效。 + +## 扩展 Store %{#augmenting-a-store}% + +你可以直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性: + +```js +pinia.use(() => ({ hello: 'world' })) +``` + +你也可以直接在 `store` 上设置该属性,但**可以的话,请使用返回对象的方法,这样它们就能被 devtools 自动追踪到**: + +```js +pinia.use(({ store }) => { + store.hello = 'world' +}) +``` + +任何由插件返回的属性都会被 devtools 自动追踪,所以如果你想在 devtools 中调试 `hello` 属性,为了使 devtools 能追踪到 `hello`,请确保**在 dev 模式下**将其添加到 `store._customProperties` 中: + +```js +// 上文示例 +pinia.use(({ store }) => { + store.hello = 'world' + // 确保你的构建工具能处理这个问题,webpack 和 vite 在默认情况下应该能处理。 + if (process.env.NODE_ENV === 'development') { + // 添加你在 store 中设置的键值 + store._customProperties.add('hello') + } +}) +``` + +值得注意的是,每个 store 都被 [`reactive`](https://cn.vuejs.org/api/reactivity-core.html#reactive)包装过,所以可以自动解包任何它所包含的 Ref(`ref()`、`computed()`...)。 + +```js +const sharedRef = ref('shared') +pinia.use(({ store }) => { + // 每个 store 都有单独的 `hello` 属性 + store.hello = ref('secret') + // 它会被自动解包 + store.hello // 'secret' + + // 所有的 store 都在共享 `shared` 属性的值 + store.shared = sharedRef + store.shared // 'shared' +}) +``` + +这就是在没有 `.value` 的情况下你依旧可以访问所有计算属性的原因,也是它们为什么是响应式的原因。 + +### 添加新的 state %{#adding-new-state}% + +如果你想给 store 添加新的 state 属性,或者在激活过程中使用的属性,**你必须同时在两个地方添加它**。 + +- 在 `store` 上,然后你才可以用 `store.myState` 访问它。 +- 在 `store.$state` 上,然后你才可以在 devtools 中使用它,并且,**在 SSR 时被正确序列化(serialized)**。 + +除此之外,你肯定也会使用 `ref()`(或其他响应式 API),以便在不同的读取中共享相同的值: + +```js +import { toRef, ref } from 'vue' + +pinia.use(({ store }) => { + // 为了正确地处理 SSR,我们需要确保我们没有重写任何一个 + // 现有的值 + if (!Object.prototype.hasOwnProperty(store.$state, 'hasError')) { + // 在插件中定义 hasError,因此每个 store 都有各自的 + // hasError 状态 + const hasError = ref(false) + // 在 `$state` 上设置变量,允许它在 SSR 期间被序列化。 + store.$state.hasError = hasError + } + // 我们需要将 ref 从 state 转移到 store + // 这样的话,两种方式:store.hasError 和 store.$state.hasError 都可以访问 + // 并且共享的是同一个变量 + // 查看 https://cn.vuejs.org/api/reactivity-utilities.html#toref + store.hasError = toRef(store.$state, 'hasError') + + // 在这种情况下,最好不要返回 `hasError` + // 因为它将被显示在 devtools 的 `state` 部分 + // 如果我们返回它,devtools 将显示两次。 +}) +``` + +需要注意的是,在一个插件中, state 变更或添加(包括调用 `store.$patch()`)都是发生在 store 被激活之前,**因此不会触发任何订阅函数**。 + +:::warning +如果你使用的是 **Vue 2**,Pinia 与 Vue 一样,受限于[相同的响应式限制](https://v2.cn.vuejs.org/v2/guide/reactivity.html#检测变化的注意事项)。在创建新的 state 属性时,如 `secret` 和 `hasError`,你需要使用 `Vue.set()` (Vue 2.7) 或者 `@vue/composition-api` 的 `set()` (Vue < 2.7)。 + +```js +import { set, toRef } from '@vue/composition-api' +pinia.use(({ store }) => { + if (!Object.prototype.hasOwnProperty(store.$state, 'hello')) { + const secretRef = ref('secret') + // 如果这些数据是要在 SSR 过程中使用的 + // 你应该将其设置在 `$state' 属性上 + // 这样它就会被序列化并在激活过程中被接收 + set(store.$state, 'secret', secretRef) + // 直接在 store 里设置,这样你就可以访问它了。 + // 两种方式都可以:`store.$state.secret` / `store.secret`。 + set(store, 'secret', secretRef) + store.secret // 'secret' + } +}) +``` + +::: + +## 添加新的外部属性 %{#adding-new-external-properties}% + +当添加外部属性、第三方库的类实例或非响应式的简单值时,你应该先用 `markRaw()` 来包装一下它,再将它传给 pinia。下面是一个在每个 store 中添加路由器的例子: + +```js +import { markRaw } from 'vue' +// 根据你的路由器的位置来调整 +import { router } from './router' + +pinia.use(({ store }) => { + store.router = markRaw(router) +}) +``` + +## 在插件中调用 `$subscribe` %{#calling-subscribe-inside-plugins}% + +你也可以在插件中使用 [store.$subscribe](./state.md#subscribing-to-the-state) 和 [store.$onAction](./actions.md#subscribing-to-actions) 。 + +```ts +pinia.use(({ store }) => { + store.$subscribe(() => { + // 响应 store 变化 + }) + store.$onAction(() => { + // 响应 store actions + }) +}) +``` + +## 添加新的选项 %{#adding-new-options}% + +在定义 store 时,可以创建新的选项,以便在插件中使用它们。例如,你可以创建一个 `debounce` 选项,允许你让任何 action 实现防抖。 + +```js +defineStore('search', { + actions: { + searchContacts() { + // ... + }, + }, + + // 这将在后面被一个插件读取 + debounce: { + // 让 action searchContacts 防抖 300ms + searchContacts: 300, + }, +}) +``` + +然后,该插件可以读取该选项来包装 action,并替换原始 action: + +```js +// 使用任意防抖库 +import debounce from 'lodash/debounce' + +pinia.use(({ options, store }) => { + if (options.debounce) { + // 我们正在用新的 action 来覆盖这些 action + return Object.keys(options.debounce).reduce((debouncedActions, action) => { + debouncedActions[action] = debounce( + store[action], + options.debounce[action] + ) + return debouncedActions + }, {}) + } +}) +``` + +注意,在使用 setup 语法时,自定义选项作为第 3 个参数传递: + +```js +defineStore( + 'search', + () => { + // ... + }, + { + // 这将在后面被一个插件读取 + debounce: { + // 让 action searchContacts 防抖 300ms + searchContacts: 300, + }, + } +) +``` + +## TypeScript + +上述一切功能都有类型支持,所以你永远不需要使用 `any` 或 `@ts-ignore`。 + +### 标注插件类型 %{#typing-plugins}% + +一个 Pinia 插件可按如下方式实现类型标注: + +```ts +import { PiniaPluginContext } from 'pinia' + +export function myPiniaPlugin(context: PiniaPluginContext) { + // ... +} +``` + +### 为新的 store 属性添加类型 %{#typing-new-store-properties}% + +当在 store 中添加新的属性时,你也应该扩展 `PiniaCustomProperties` 接口。 + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomProperties { + // 通过使用一个 setter,我们可以允许字符串和引用。 + set hello(value: string | Ref) + get hello(): string + + // 你也可以定义更简单的值 + simpleNumber: number + } +} +``` + +然后,就可以安全地写入和读取它了: + +```ts +pinia.use(({ store }) => { + store.hello = 'Hola' + store.hello = ref('Hola') + + store.simpleNumber = Math.random() + // @ts-expect-error: we haven't typed this correctly + store.simpleNumber = ref(Math.random()) +}) +``` + +`PiniaCustomProperties` 是一个通用类型,允许你引用 store 的属性。思考一下这个例子,如果把初始选项复制成 `$options`(这只对 option store 有效),如何标注类型: + +```ts +pinia.use(({ options }) => ({ $options: options })) +``` + +我们可以通过使用 `PiniaCustomProperties` 的4种通用类型来标注类型: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomProperties { + $options: { + id: Id + state?: () => S + getters?: G + actions?: A + } + } +} +``` + +:::tip +当在泛型中扩展类型时,它们的名字必须**与源代码中完全一样**。`Id` 不能被命名为 `id` 或 `I` ,`S` 不能被命名为 `State`。下面是每个字母代表的含义: + +- S: State +- G: Getters +- A: Actions +- SS: Setup Store / Store + +::: + +### 为新的 state 添加类型 %{#typing-new-state}% + +当添加新的 state 属性(包括 `store` 和 `store.$state` )时,你需要将类型添加到 `PiniaCustomStateProperties` 中。与 `PiniaCustomProperties` 不同的是,它只接收 `State` 泛型: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface PiniaCustomStateProperties { + hello: string + } +} +``` + +### 为新的定义选项添加类型 %{#typing-new-creation-options}% + +当为 `defineStore()` 创建新选项时,你应该扩展 `DefineStoreOptionsBase`。与 `PiniaCustomProperties` 不同的是,它只暴露了两个泛型:State 和 Store 类型,允许你限制定义选项的可用类型。例如,你可以使用 action 的名称: + +```ts +import 'pinia' + +declare module 'pinia' { + export interface DefineStoreOptionsBase { + // 任意 action 都允许定义一个防抖的毫秒数 + debounce?: Partial, number>> + } +} +``` + +:::tip +还有一个可以从一个 store 类型中提取 *getter* 的 `StoreGetters` 类型。你也可以且**只可以**通过扩展 `DefineStoreOptions` 或 `DefineSetupStoreOptions` 类型来扩展 *setup store* 或 *option store* 的选项。 +::: + +## Nuxt.js %{#nuxt-js}% + +当[在 Nuxt 中使用 pinia](../ssr/nuxt.md) 时,你必须先创建一个 [Nuxt 插件](https://nuxtjs.org/docs/2.x/directory-structure/plugins)。这样你才能访问到 `pinia` 实例: + +```ts +// plugins/myPiniaPlugin.js +import { PiniaPluginContext } from 'pinia' +import { Plugin } from '@nuxt/types' + +function MyPiniaPlugin({ store }: PiniaPluginContext) { + store.$subscribe((mutation) => { + // 响应 store 变更 + console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`) + }) + + // 请注意,如果你使用的是 TS,则必须添加类型。 + return { creationTime: new Date() } +} + +const myPlugin: Plugin = ({ $pinia }) => { + $pinia.use(MyPiniaPlugin) +} + +export default myPlugin +``` + +注意上面的例子使用的是 TypeScript。如果你使用的是 `.js` 文件,你必须删除类型标注 `PiniaPluginContext` 和 `Plugin` 以及它们的导入语句。 diff --git a/packages/docs-new/zh/core-concepts/state.md b/packages/docs-new/zh/core-concepts/state.md new file mode 100644 index 00000000..25db826a --- /dev/null +++ b/packages/docs-new/zh/core-concepts/state.md @@ -0,0 +1,261 @@ +# State %{#state}% + + + +在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。 + +```js +import { defineStore } from 'pinia' + +const useStore = defineStore('storeId', { + // 为了完整类型推理,推荐使用箭头函数 + state: () => { + return { + // 所有这些属性都将自动推断出它们的类型 + count: 0, + name: 'Eduardo', + isAdmin: true, + items: [], + hasChanged: true, + } + }, +}) +``` + +:::tip +如果你使用的是 Vue 2,你在 `state` 中创建的数据与 Vue 实例中的 `data` 遵循同样的规则,即 state 对象必须是清晰的,当你想向其**添加新属性**时,你需要调用 `Vue.set()` 。**参考:[Vue#data](https://v2.cn.vuejs.org/v2/api/#data)**。 +::: + +## TypeScript %{#typescript}% + +你并不需要做太多努力就能使你的 state 兼容 TS。 Pinia 会自动推断出你的 state 的类型,但在一些情况下,你得用一些方法来帮它一把。 + +```ts +const useStore = defineStore('storeId', { + state: () => { + return { + // 用于初始化空列表 + userList: [] as UserInfo[], + // 用于尚未加载的数据 + user: null as UserInfo | null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` + +如果你愿意,你可以用一个接口定义 state,并添加 `state()` 的返回值的类型。 + +```ts +interface State { + userList: UserInfo[] + user: UserInfo | null +} + +const useStore = defineStore('storeId', { + state: (): State => { + return { + userList: [], + user: null, + } + }, +}) + +interface UserInfo { + name: string + age: number +} +``` + +## 访问 `state` %{#accessing-the-state}% + +默认情况下,你可以通过 `store` 实例访问 state,直接对其进行读写。 + +```js +const store = useStore() + +store.count++ +``` + +## 重置 state %{#resetting-the-state}% + +你可以通过调用 store 的 `$reset()` 方法将 state 重置为初始值。 + +```js +const store = useStore() + +store.$reset() +``` + +### 使用选项式 API 的用法 %{#usage-with-the-options-api}% + + + +在下面的例子中,你可以假设相关 store 已经创建了: + +```js +// 示例文件路径: +// ./src/stores/counter.js + +import { defineStore } from 'pinia' + +const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0, + }), +}) +``` + +如果你不能使用组合式 API,但你可以使用 `computed`,`methods`,...,那你可以使用 `mapState()` 辅助函数将 state 属性映射为只读的计算属性: + +```js +import { mapState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // 可以访问组件中的 this.count + // 与从 store.count 中读取的数据相同 + ...mapState(useCounterStore, ['count']) + // 与上述相同,但将其注册为 this.myOwnName + ...mapState(useCounterStore, { + myOwnName: 'count', + // 你也可以写一个函数来获得对 store 的访问权 + double: store => store.count * 2, + // 它可以访问 `this`,但它没有标注类型... + magicValue(store) { + return store.someGetter + this.count + this.double + }, + }), + }, +} +``` + +#### 可修改的 state %{#modifiable-state}% + +如果你想修改这些 state 属性(例如,如果你有一个表单),你可以使用 `mapWritableState()` 作为代替。但注意你不能像 `mapState()` 那样传递一个函数: + +```js +import { mapWritableState } from 'pinia' +import { useCounterStore } from '../stores/counter' + +export default { + computed: { + // 可以访问组件中的 this.count,并允许设置它。 + // this.count++ + // 与从 store.count 中读取的数据相同 + ...mapWritableState(useCounterStore, ['count']) + // 与上述相同,但将其注册为 this.myOwnName + ...mapWritableState(useCounterStore, { + myOwnName: 'count', + }), + }, +} +``` + +:::tip +对于像数组这样的集合,你并不一定需要使用 `mapWritableState()`,`mapState()` 也允许你调用集合上的方法,除非你想用 `cartItems = []` 替换整个数组。 +::: + +## 变更 state %{#mutating-the-state}% + + + +除了用 `store.count++` 直接改变 store,你还可以调用 `$patch` 方法。它允许你用一个 `state` 的补丁对象在同一时间更改多个属性: + +```js +store.$patch({ + count: store.count + 1, + age: 120, + name: 'DIO', +}) +``` + +不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 `splice` 操作)都需要你创建一个新的集合。因此,`$patch` 方法也接受一个函数来组合这种难以用补丁对象实现的变更。 + +```js +store.$patch((state) => { + state.items.push({ name: 'shoes', quantity: 1 }) + state.hasChanged = true +}) +``` + + + +两种变更 store 方法的主要区别是,`$patch()` 允许你将多个变更归入 devtools 的同一个条目中。同时请注意,**直接修改 `state`,`$patch()` 也会出现在 devtools 中**,而且可以进行 time travel(在 Vue 3 中还没有)。 + +## 替换 `state` %{#replacing-the-state}% + +你**不能完全替换掉** store 的 state,因为那样会破坏其响应性。但是,你可以 *patch* 它。 + +```js +// 这实际上并没有替换`$state` +store.$state = { count: 24 } +// 在它内部调用 `$patch()`: +store.$patch({ count: 24 }) +``` + +你也可以通过变更 `pinia` 实例的 `state` 来设置整个应用的初始 state。这常用于 [SSR 中的激活过程](../ssr/#state-hydration)。 + +```js +pinia.state.value = {} +``` + +## 订阅 state %{#subscribing-to-the-state}% + +类似于 Vuex 的 [subscribe 方法](https://vuex.vuejs.org/zh/api/index.html#subscribe),你可以通过 store 的 `$subscribe()` 方法侦听 state 及其变化。比起普通的 `watch()`,使用 `$subscribe()` 的好处是 *subscriptions* 在 *patch* 后只触发一次(例如,当使用上面的函数版本时)。 + +```js +cartStore.$subscribe((mutation, state) => { + // import { MutationType } from 'pinia' + mutation.type // 'direct' | 'patch object' | 'patch function' + // 和 cartStore.$id 一样 + mutation.storeId // 'cart' + // 只有 mutation.type === 'patch object'的情况下才可用 + mutation.payload // 传递给 cartStore.$patch() 的补丁对象。 + + // 每当状态发生变化时,将整个 state 持久化到本地存储。 + localStorage.setItem('cart', JSON.stringify(state)) +}) +``` + +默认情况下,*state subscription* 会被绑定到添加它们的组件上(如果 store 在组件的 `setup()` 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 `{ detached: true }` 作为第二个参数,以将 *state subscription* 从当前组件中*分离*: + +```js +export default { + setup() { + const someStore = useSomeStore() + + // 在组件被卸载后,该订阅依旧会被保留。 + someStore.$subscribe(callback, { detached: true }) + + // ... + }, +} +``` + +:::tip +你可以在 `pinia` 实例上侦听整个 state。 + +```js +watch( + pinia.state, + (state) => { + // 每当状态发生变化时,将整个 state 持久化到本地存储。 + localStorage.setItem('piniaState', JSON.stringify(state)) + }, + { deep: true } +) +``` + +::: diff --git a/packages/docs-new/zh/getting-started.md b/packages/docs-new/zh/getting-started.md new file mode 100644 index 00000000..ed4aaef3 --- /dev/null +++ b/packages/docs-new/zh/getting-started.md @@ -0,0 +1,61 @@ +## 安装 %{#installation}% + +用你喜欢的包管理器安装 `pinia`: + +```bash +yarn add pinia +# 或者使用 npm +npm install pinia +``` + +:::tip +如果你的应用使用的是 Vue 2,你还需要安装组合式 API 包:`@vue/composition-api`。如果你使用的是 Nuxt,你应该参考[这篇指南](/ssr/nuxt.md)。 +::: + +如果你正在使用 Vue CLI,你可以试试这个[**非官方插件**](https://github.com/wobsoriano/vue-cli-plugin-pinia)。 + +创建一个 pinia 实例(根 store)并将其传递给应用: + +```js {2,5-6,8} +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' + +const pinia = createPinia() +const app = createApp(App) + +app.use(pinia) +app.mount('#app') +``` + +如果你使用的是 Vue 2,你还需要安装一个插件,并在应用的根部注入创建的 `pinia`: + +```js {1,3-4,12} +import { createPinia, PiniaVuePlugin } from 'pinia' + +Vue.use(PiniaVuePlugin) +const pinia = createPinia() + +new Vue({ + el: '#app', + // 其他配置... + // ... + // 请注意,同一个`pinia'实例 + // 可以在同一个页面的多个 Vue 应用中使用。 + pinia, +}) +``` + +这样才能提供 devtools 的支持。在 Vue 3 中,一些功能仍然不被支持,如 time traveling 和编辑,这是因为 vue-devtools 还没有相关的 API,但 devtools 也有很多针对 Vue 3 的专属功能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口(因此不能与 Vuex 一起使用)。 + +## Store 是什么?{#what-is-a-store} + +Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,**它承载着全局状态**。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有**三个概念**,[state](./core-concepts/state.md)、[getter](./core-concepts/getters.md) 和 [action](./core-concepts/actions.md),我们可以假设这些概念相当于组件中的 `data`、 `computed` 和 `methods`。 + +## 应该在什么时候使用 Store? %{#when-should-i-use-a-store}% + +一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。 + +另一方面,你应该避免在 Store 中引入那些原本可以在组件中保存的本地数据,例如,一个元素在页面中的可见性。 + +并非所有的应用都需要访问全局状态,但如果你的应用确实需要一个全局状态,那 Pinia 将使你的开发过程更轻松。 diff --git a/packages/docs-new/zh/index.md b/packages/docs-new/zh/index.md new file mode 100644 index 00000000..3df37f70 --- /dev/null +++ b/packages/docs-new/zh/index.md @@ -0,0 +1,43 @@ +--- +layout: home + +title: Pinia +titleTemplate: The intuitive store for Vue.js + +# TODO: translate text and tagline + +hero: + name: Pinia + text: The intuitive store for Vue.js + tagline: Type Safe, Extensible, and Modular by design. Forget you are even using a store. + image: + src: /logo.svg + alt: Pinia + actions: + - theme: brand + text: 开始使用 + link: /zh/introduction + - theme: alt + text: Demo 演示 + link: https://stackblitz.com/github/piniajs/example-vue-3-vite + +features: + - title: 💡 所见即所得 + details: 与组件类似的 Store。其 API 的设计旨在让你编写出更易组织的 store。 + - title: 🔑 类型安全 + details: 类型可自动推断,即使在 JavaScript 中亦可为你提供自动补全功能! + - title: ⚙️ 开发工具支持 + details: 不管是 Vue 2 还是 Vue 3,支持 Vue devtools 钩子的 Pinia 都能给你更好的开发体验。 + - title: 🔌 可扩展性 + details: 可通过事务、同步本地存储等方式扩展 Pinia,以响应 store 的变更。 + - title: 🏗 模块化设计 + details: 可构建多个 Store 并允许你的打包工具自动拆分它们。 + - title: 📦 极致轻量化 + details: Pinia 大小只有 1kb 左右,你甚至可能忘记它的存在! +--- + + + + diff --git a/packages/docs-new/zh/introduction.md b/packages/docs-new/zh/introduction.md new file mode 100644 index 00000000..bac83780 --- /dev/null +++ b/packages/docs-new/zh/introduction.md @@ -0,0 +1,192 @@ +# 简介 %{#introduction}% + + + +Pinia [起始](https://github.com/vuejs/pinia/commit/06aeef54e2cad66696063c62829dac74e15fd19e)于 2019 年 11 月左右的一次实验,其目的是设计一个拥有[组合式 API](https://github.com/vuejs/composition-api) 的 Vue 状态管理库。从那时起,我们就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API,我们的初心至今没有改变。除了**安装**和 **SSR** 两章之外,其余章节中提到的 API 均支持 Vue 2 和 Vue 3。虽然本文档主要是面向 Vue 3 的用户,但在必要时会标注出 Vue 2 的内容,因此 Vue 2 和 Vue 3 的用户都可以阅读本文档。 + +## 为什么你应该使用 Pinia?{#why-should-i-use-pinia} + +Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 `export const state = reactive({})` 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能: + +- Devtools 支持 + - 追踪 actions、mutations 的时间线 + - 在组件中展示它们所用到的 Store + - 让调试更容易的 Time travel +- 热更新 + - 不必重载页面即可修改 Store + - 开发时可保持当前的 State +- 插件:可通过插件扩展 Pinia 功能 +- 为 JS 开发者提供适当的 TypeScript 支持以及**自动补全**功能。 +- 支持服务端渲染 + +## 基础示例 %{#basic-example}% + +下面就是 pinia API 的基本用法 (为继续阅读本简介请确保你已阅读过了[开始](./getting-started.md)章节)。你可以先创建一个 Store: + +```js +// stores/counter.js +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', { + state: () => { + return { count: 0 } + }, + // 也可以这样定义 + // state: () => ({ count: 0 }) + actions: { + increment() { + this.count++ + }, + }, +}) +``` + +然后你就可以在一个组件中使用该 store 了: + +```js +import { useCounterStore } from '@/stores/counter' + +export default { + setup() { + const counter = useCounterStore() + + counter.count++ + // 带有自动补全 ✨ + counter.$patch({ count: counter.count + 1 }) + // 或者使用 action 代替 + counter.increment() + }, +} +``` + +为实现更多高级用法,你甚至可以使用一个函数(与组件 `setup()` 类似)来定义一个 Store: + +```js +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + function increment() { + count.value++ + } + + return { count, increment } +}) +``` + +如果你还不熟悉 setup() 函数和组合式 API,别担心,Pinia 也提供了一组类似 Vuex 的 [映射 state 的辅助函数](https://vuex.vuejs.org/zh/guide/state.html#mapstate-辅助函数)。你可以用和之前一样的方式来定义 Store,然后通过 `mapStores()`、`mapState()` 或 `mapActions()` 访问: + +```js {22,24,28} +const useCounterStore = defineStore('counter', { + state: () => ({ count: 0 }), + getters: { + double: (state) => state.count * 2, + }, + actions: { + increment() { + this.count++ + }, + }, +}) + +const useUserStore = defineStore('user', { + // ... +}) + +export default { + computed: { + // 其他计算属性 + // ... + // 允许访问 this.counterStore 和 this.userStore + ...mapStores(useCounterStore, useUserStore) + // 允许读取 this.count 和 this.double + ...mapState(useCounterStore, ['count', 'double']), + }, + methods: { + // 允许读取 this.increment() + ...mapActions(useCounterStore, ['increment']), + }, +} +``` + +你将会在核心概念部分了解到更多关于每个**映射辅助函数**的信息。 + +## 为什么取名 *Pinia*?{#why-pinia} + +Pinia (发音为 `/piːnjʌ/`,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 *pineapple*,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。 + +## 更真实的示例 %{#a-more-realistic-example}% + +这是一个更完整的 Pinia API 示例,在 JavaScript 中也使用了类型提示。对于某些开发者来说,可能足以在不进一步阅读的情况下直接开始阅读本节内容,但我们仍然建议你先继续阅读文档的其余部分,甚至跳过此示例,在阅读完所有**核心概念**之后再回来。 + +```js +import { defineStore } from 'pinia' + +export const useTodos = defineStore('todos', { + state: () => ({ + /** @type {{ text: string, id: number, isFinished: boolean }[]} */ + todos: [], + /** @type {'all' | 'finished' | 'unfinished'} */ + filter: 'all', + // 类型将自动推断为 number + nextId: 0, + }), + getters: { + finishedTodos(state) { + // 自动补全! ✨ + return state.todos.filter((todo) => todo.isFinished) + }, + unfinishedTodos(state) { + return state.todos.filter((todo) => !todo.isFinished) + }, + /** + * @returns {{ text: string, id: number, isFinished: boolean }[]} + */ + filteredTodos(state) { + if (this.filter === 'finished') { + // 调用其他带有自动补全的 getters ✨ + return this.finishedTodos + } else if (this.filter === 'unfinished') { + return this.unfinishedTodos + } + return this.todos + }, + }, + actions: { + // 接受任何数量的参数,返回一个 Promise 或不返回 + addTodo(text) { + // 你可以直接变更该状态 + this.todos.push({ text, id: this.nextId++, isFinished: false }) + }, + }, +}) +``` + +## 对比 Vuex %{#comparison-with-vuex}% + +Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。 + +与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。 + +### RFC %{#rfcs}% + +最初,Pinia 没有经过任何 RFC 的流程。我基于自己开发应用的经验,同时通过阅读其他人的代码,为使用 Pinia 的用户工作,以及在 Discord 上回答问题等方式验证了一些想法。 +这些经历使我产出了这样一个可用的解决方案,并适应了各种场景和应用规模。我会一直在保持其核心 API 不变的情况下发布新版本,同时不断优化本库。 + +现在 Pinia 已经成为推荐的状态管理解决方案,它和 Vue 生态系统中的其他核心库一样,都要经过 RFC 流程,它的 API 也已经进入稳定状态。 + +### 对比 Vuex 3.x/4.x %{#comparison-with-vuex-3-x-4-x}% + +> Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。 + +Pinia API 与 Vuex(<=4) 也有很多不同,即: + +- *mutation* 已被弃用。它们经常被认为是**极其冗余的**。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。 +- 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。 +- 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。 +- 无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。 +- 不再有嵌套结构的**模块**。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。**你甚至可以让 Stores 有循环依赖关系**。 +- 不再有**可命名的模块**。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。 + +关于如何将现有 Vuex(<=4) 的项目转化为使用 Pinia 的更多详细说明,请参阅 [Vuex 迁移指南](./cookbook/migration-vuex.md)。 diff --git a/packages/docs-new/zh/ssr/index.md b/packages/docs-new/zh/ssr/index.md new file mode 100644 index 00000000..557ae6e3 --- /dev/null +++ b/packages/docs-new/zh/ssr/index.md @@ -0,0 +1,111 @@ +# 服务端渲染 (SSR) %{#server-side-rendering-ssr}% + +:::tip +如果你使用的是 **Nuxt.js**,你需要阅读的是[**这些说明文档**](./nuxt.md)。 +::: + +只要你只在 `setup` 函数、`getter` 和 `action` 的顶部调用你定义的 `useStore()` 函数,那么使用 Pinia 创建 store 对于 SSR 来说应该是开箱即用的: + +```js +export default defineComponent({ + setup() { + // 这样做的原因是 Pinia 知道 + // `setup()` 中运行的应用是什么 + const main = useMainStore() + return { main } + }, +}) +``` + +## 在 `setup()` 外部使用 store %{#using-the-store-outside-of-setup}% + +如果你需要在其他地方使用 store,你需要将[原本被传递给应用](#install-the-plugin) 的 `pinia` 实例传递给 `useStore()` 函数: + +```js +const pinia = createPinia() +const app = createApp(App) + +app.use(router) +app.use(pinia) + +router.beforeEach((to) => { + // ✅这会正常工作,因为它确保了正确的 store 被用于 + // 当前正在运行的应用 + const main = useMainStore(pinia) + + if (to.meta.requiresAuth && !main.isLoggedIn) return '/login' +}) +``` + +Pinia 会将自己作为 `$pinia` 添加到你的应用中,所以你可以在 `serverPrefetch()` 等函数中使用它。 + +```js +export default { + serverPrefetch() { + const store = useStore(this.$pinia) + }, +} +``` + +## State 激活 %{#state-hydration}% + +为了激活初始 state,你需要确保 rootState 包含在 HTML 中的某个地方,以便 Pinia 稍后能够接收到它。根据你服务端所渲染的内容,**为了安全你应该转义 state**。我们推荐 Nuxt.js 目前使用的 [@nuxt/devalue](https://github.com/nuxt-contrib/devalue): + +```js +import devalue from '@nuxt/devalue' +import { createPinia } from 'pinia' +// 检索服务端的 rootState +const pinia = createPinia() +const app = createApp(App) +app.use(router) +app.use(pinia) + +// 渲染页面后,rootState 被建立, +// 可以直接在 `pinia.state.value`上读取。 + +// 序列化,转义(如果 state 的内容可以被用户改变,这点就非常重要,几乎都是这样的) +// 并将其放置在页面的某处 +// 例如,作为一个全局变量。 +devalue(pinia.state.value) +``` + +根据你服务端所渲染的内容,你将设置一个**初始状态**变量,该变量将在 HTML 中被序列化。你还应该保护自己免受 XSS 攻击。例如,在 [vite-ssr](https://github.com/frandiox/vite-ssr)中你可以使用[`transformState` 选项](https://github.com/frandiox/vite-ssr#state-serialization) 以及 `@nuxt/devalue`: + +```js +import devalue from '@nuxt/devalue' + +export default viteSSR( + App, + { + routes, + transformState(state) { + return import.meta.env.SSR ? devalue(state) : state + }, + }, + ({ initialState }) => { + // ... + if (import.meta.env.SSR) { + // 序列化并设置为 window.__INITIAL_STATE__ + initialState.pinia = pinia.state.value + } else { + // 在客户端,我们恢复 state + pinia.state.value = initialState.pinia + } + } +) +``` + +你可以根据你的需要使用 `@nuxt/devalue` 的[其他替代品](https://github.com/nuxt-contrib/devalue#see-also),例如,你也可以用 `JSON.stringify()`/`JSON.parse()` 来序列化和解析你的 state,**这样你可以把性能提高很多。** + +也可以根据你的环境调整这个策略。但确保在客户端调用任何 `useStore()` 函数之前,激活 pinia 的 state。例如,如果我们将 state 序列化为一个 `