]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
docs: updates after effectScope
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 22 Jul 2021 15:23:04 +0000 (17:23 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 22 Jul 2021 15:23:04 +0000 (17:23 +0200)
docs/cookbook/index.md
docs/core-concepts/actions.md
docs/core-concepts/index.md
docs/core-concepts/plugins.md
docs/core-concepts/state.md
docs/getting-started.md
docs/introduction.md

index 2b2cc30f65201a417f2da2f0a25803f380097c19..4337a2f0c977db51b6efc8de915356d1d4a20fda 100644 (file)
@@ -1,5 +1,6 @@
 # Cookbook
 
+- [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.
index ce59170fc606be874e7316683c2202fd5af98237..aaa4a37be02d82ee92d3920ccdc7e103e64b4890 100644 (file)
@@ -118,11 +118,11 @@ export default {
 }
 ```
 
-## Watching actions
+## Subscribing to actions
 
 > [Give feedback about `$onAction()`](https://github.com/posva/pinia/issues/240)
 
-It is possible to observe actions and their outcome with `store.$onAction()`. This
+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 change the returned value of the action. `onError` allows you to stop the error from propagating. These are useful for tracking errors at runtime, similar to [what is explaining in 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.
 
@@ -163,4 +163,17 @@ const unsubscribe = someStore.$onAction(
 unsubscribe()
 ```
 
-By default, action listeners 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.
+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 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 after the component is unmounted
+    someStore.$onAction(callback, true)
+
+    // ...
+  },
+}
+```
index 44b088059df193c947eb9e065164806f7082ea84..88d219f23d0b64cf38be4e91dcdfdc1064a50ca8 100644 (file)
@@ -1,6 +1,6 @@
 # Defining a Store
 
-Before diving into core concepts, we need to know that a store is defined using `defineStore()` and that it requires an `id` property, **unique** across all of your stores:
+Before diving into core concepts, we need to know that a store is defined using `defineStore()` and that it requires an **unique** name, passed as the first argument:
 
 ```js
 import { defineStore } from 'pinia'
@@ -12,7 +12,7 @@ export const useStore = defineStore('main', {
 })
 ```
 
-The `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.
+This _name_, also referred 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.
 
 ## Using the store
 
index 58ad6121a38f8b3fb37b39949a7e2ebe4e952756..703976f2c70721b37e288118da5ca522d0bf9bab 100644 (file)
@@ -30,7 +30,7 @@ const store = useStore()
 store.secret // 'the cake is a lie'
 ```
 
-This is useful to add global objects like the router, modals, or toasts.
+This is useful to add global objects like the router, modal, or toast managers.
 
 ## Introduction
 
@@ -52,7 +52,7 @@ This function is then passed to `pinia` with `pinia.use()`:
 pinia.use(myPiniaPlugin)
 ```
 
-It will get executed **every time `useStore()`** is called to be able to extend them. This is a limitation of the current implementation until [the effectScope RFC](https://github.com/vuejs/rfcs/pull/212) is merged.
+Plugins are only applied to stores **created after `pinia` is passed to the app**, otherwise they won't be applied.
 
 ## Augmenting a Store
 
@@ -70,6 +70,19 @@ pinia.use(({ store }) => {
 })
 ```
 
+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') {
+    store._customProperties.add('secret')
+  }
+})
+```
+
 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
@@ -106,35 +119,19 @@ pinia.use(({ store }) => {
   // it gets automatically unwrapped
   store.secret // 'secret'
 
-  // we need to check if the state has been added yet because of
-  // the limitation mentioned during the introduction
-  if (!store.$state.hasOwnProperty('hasError')) {
-    // Each store has its own `hasError`
-    store.$state.hasError = ref(false)
-  }
+  const hasError = ref(false)
+  store.$state.hasError = hasError
   // this one must always be set
   store.hasError = toRef(store.$state, 'hasError')
-})
-```
-
-**Note**: If you are using Vue 2, make sure to use `set` (from `@vue/composition-api`) or `Vue.set` as mentioned in the [State page](./state.md#state) when creating new properties like `secret` and `hasError` in the example above.
 
-Any property _returned_ by a plugin will be automatically tracked by devtools so in order to make `hasError` 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.$state.secret = globalSecret
-  store.secret = globalSecret
-  // make sure your bundler handle this. webpack and vite should do it by default
-  if (process.env.NODE_ENV === 'development') {
-    store._customProperties.add('secret')
-  }
+  // 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.
 })
 ```
 
 :::warning
-If you are using **Vue 2**, Pinia is subject to the [same reactivity caveats](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) as Vue. You will need to use `set` from `@vue/composition-api`:
+If you are using **Vue 2**, Pinia is subject to the [same reactivity caveats](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) as Vue. You will need to use `set` from `@vue/composition-api` when creating new state properties like `secret` and `hasError`:
 
 ```js
 import { set } from '@vue/composition-api'
@@ -144,11 +141,11 @@ pinia.use(({ store }) => {
     // 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, 'hello', secretRef)
+    set(store.$state, 'secret', secretRef)
     // set it directly on the store too so you can access it
-    // both ways: `store.$state.hello` / `store.hello`
-    set(store, 'hello', secretRef)
-    store.hello // 'secret'
+    // both ways: `store.$state.secret` / `store.secret`
+    set(store, 'secret', secretRef)
+    store.secret // 'secret'
   }
 })
 ```
@@ -171,22 +168,19 @@ pinia.use(({ store }) => {
 
 ## Calling `$subscribe` inside plugins
 
-Because of the limitation mentioned above about plugins being invoked **every time `useStore()` is called**, it's important to avoid _subscribing_ multiple times by keeping track of the registered subscriptions:
+You can use [store.$subscribe](./state.md#subscribing-to-the-state) and [store.$onAction](./actions.md#subscribing-to-actions) inside plugins too:
 
 ```ts
-let isRegistered
 pinia.use(({ store }) => {
-  if (!isRegistered) {
-    store.$subscribe(() => {
-      // react to store changes
-    })
-    isRegistered = true
-  }
+  store.$subscribe(() => {
+    // react to store changes
+  })
+  store.$onAction(() => {
+    // react to store actions
+  })
 })
 ```
 
-The same is true for `store.$onAction()`.
-
 ## 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:
@@ -215,6 +209,7 @@ import debounce from 'lodash/debunce'
 
 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],
@@ -228,6 +223,8 @@ pinia.use(({ options, store }) => {
 
 ## 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:
@@ -249,7 +246,12 @@ import 'pinia'
 
 declare module 'pinia' {
   export interface PiniaCustomProperties {
-    hello: string
+    // by using a setter we can allow both strings and refs
+    set hello(value: string | Ref<string>)
+    get hello(): string
+
+    // you can define simpler values too
+    simpleNumber: number
   }
 }
 ```
@@ -259,8 +261,11 @@ It can then be written and read safely:
 ```ts
 pinia.use(({ store }) => {
   store.hello = 'Hola'
-  // @ts-expect-error: this will still add a string because refs get unwrapped
   store.hello = ref('Hola')
+
+  store.number = Math.random()
+  // @ts-expect-error: we haven't typed this correctly
+  store.number = ref(Math.random())
 })
 ```
 
index 67bd7a0ddbf5df6ba823db7b082de36406e2454e..b16d5945df53063f01787526b1cc2d33be472712 100644 (file)
@@ -133,59 +133,51 @@ You can also replace the whole state of your application by changing the `state`
 pinia.state.value = {}
 ```
 
-## Watching the state
+## Subscribing to the state
 
-You can watch the state, similar to Vuex's [subscribe method](https://vuex.vuejs.org/api/#subscribe) by simply watching it (since it's a reactive source). Keep in mind a watcher is cleared up when the wrapping component is unmounted so you should add the watcher in your App component or outside of it if you want it to run forever.
+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
-watch(
-  pinia.state,
-  (state) => {
-    // persist the whole state to the local storage whenever it changes
-    localStorage.setItem('piniaState', JSON.stringify(state))
-  },
-  { deep: true }
-)
+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))
+})
 ```
 
-You can also observe a specific store state instead of all of them by passing a function. Here is an example to watch a store with the id `cart`:
+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 want to keep them after the component is unmounted, pass `true` as the second argument to _detach_ the _state subscription_ from the current component:
 
 ```js
-watch(
-  () => pinia.state.value.cart,
-  (cartState) => {
-    // persist the whole state to the local storage whenever it changes
-    localStorage.setItem('cart', JSON.stringify(cartState))
-  },
-  { deep: true }
-)
-```
+export default {
+  setup() {
+    const someStore = useSomeStore()
 
-Note that depending on when you create the watcher, `pinia.state.value.cart` might be `undefined`. You can also watch a store's `$state` property (this will also make typing work):
+    // this subscription will be kept after the component is unmounted
+    someStore.$subscribe(callback, true)
 
-```ts
-import { defineStore } from 'pinia'
-
-const useCartStore = defineStore('cart', {
-  // ...
-})
+    // ...
+  },
+}
+```
 
-const cartStore = useCartStore()
+:::tip
+You can watch the whole state on the `pinia` instance:
 
-// watch the whole state of the cart
+```js
 watch(
-  () => cartStore.$state,
-  () => {
-    // do something
+  pinia.state,
+  (state) => {
+    // persist the whole state to the local storage whenever it changes
+    localStorage.setItem('piniaState', JSON.stringify(state))
   },
   { deep: true }
 )
-
-// you can also watch a getter
-watch(
-  () => cartStore.totalAmount,
-  () => {
-    // do something
-  }
-)
 ```
+
+:::
index c61eec74252e25eb8535d56f368d5f4488b4cbac..bc08b5796d66bb2816e64a785e3c1eaabe0b246f 100644 (file)
@@ -12,7 +12,7 @@ npm install pinia@next
 `pinia@next` install Pinia v2 for Vue 3. If your app is using Vue 2, you need to install Pinia v1: `pinia@latest` **and** `@vue/composition-api`. If you are using Nuxt, you should follow [these instructions](/ssr/nuxt).
 :::
 
-Create a pinia (the root store) and pass it to app:
+Create a pinia (the root store) and pass it to the app:
 
 ```js
 import { createPinia } from 'pinia'
@@ -38,7 +38,7 @@ new Vue({
 })
 ```
 
-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. In Vue 2, Pinia uses the existing interface for Vuex (and can therefore not be used alongside it).
+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 are 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?
 
index fbf22f98cffbe8f5f643e32138baaac34d4d0b5a..907ac524b482df06a8fd15379a6c13dc423f543a 100644 (file)
@@ -1,6 +1,18 @@
 # Introduction
 
-Pinia [started](https://github.com/posva/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 are still 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 used no matter if you are using Vue 2 or Vue 3!
+Pinia [started](https://github.com/posva/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 are still 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** 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
+- Plugins: extend Pinia features with plugins
+- Proper TypeScript support or **autocompletion** for JS users
+- Server Side Rendering Support
 
 ## Basic example
 
@@ -35,6 +47,16 @@ export default {
 }
 ```
 
+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)
+
+  return { count }
+})
+```
+
 If you are still not into `setup()` and Composition API, don't worry, Pinia also support 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