]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat: reuse store instances when possible
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 29 Apr 2021 12:37:09 +0000 (14:37 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 29 Apr 2021 12:37:09 +0000 (14:37 +0200)
__tests__/store.spec.ts
src/rootStore.ts
src/store.ts

index c94bcc130a1cb030401ce6f91b4cd3c3ab7ed79a..6699b0eebea4801086af752b6c469fe2d81036d5 100644 (file)
@@ -1,6 +1,6 @@
 import { createPinia, defineStore, setActivePinia, Pinia } from '../src'
 import { mount } from '@vue/test-utils'
-import { getCurrentInstance, nextTick, watch } from 'vue'
+import { defineComponent, getCurrentInstance, nextTick, watch } from 'vue'
 
 describe('Store', () => {
   let pinia: Pinia
@@ -246,4 +246,32 @@ describe('Store', () => {
 
     wrapper.unmount()
   })
+
+  it('reuses stores from parent components', () => {
+    let s1, s2
+    const useStore = defineStore({ id: 'one' })
+    const pinia = createPinia()
+
+    const Child = defineComponent({
+      setup() {
+        s2 = useStore()
+      },
+      template: `child`,
+    })
+
+    mount(
+      {
+        setup() {
+          s1 = useStore()
+          return { s1 }
+        },
+        components: { Child },
+        template: `<child/>`,
+      },
+      { global: { plugins: [pinia] } }
+    )
+
+    expect(s1).toBeDefined()
+    expect(s1).toBe(s2)
+  })
 })
index 03c5761c46c9220d34db71aef0f90c9736da1548..8c115f707e1e16906e93af0eb9b7d91c8f13c8c2 100644 (file)
@@ -49,7 +49,14 @@ export const getActivePinia = () => {
 
 export const storesMap = new WeakMap<
   Pinia,
-  Map<string, [StoreWithState<string, StateTree>, StateDescriptor<StateTree>]>
+  Map<
+    string,
+    [
+      StoreWithState<string, StateTree>,
+      StateDescriptor<StateTree>,
+      InjectionKey<GenericStore>
+    ]
+  >
 >()
 
 /**
index fc3dada9897badec238f59734929a910f4afb955..cfe8a3c75abe98086a745da8616dc3961455a9b1 100644 (file)
@@ -6,6 +6,8 @@ import {
   getCurrentInstance,
   reactive,
   onUnmounted,
+  InjectionKey,
+  provide,
 } from 'vue'
 import {
   StateTree,
@@ -20,6 +22,7 @@ import {
   Method,
   DefineStoreOptions,
   StoreDefinition,
+  GenericStore,
 } from './types'
 import {
   getActivePinia,
@@ -90,7 +93,11 @@ function initStore<Id extends string, S extends StateTree>(
   $id: Id,
   buildState: () => S = () => ({} as S),
   initialState?: S | undefined
-): [StoreWithState<Id, S>, { get: () => S; set: (newValue: S) => void }] {
+): [
+  StoreWithState<Id, S>,
+  { get: () => S; set: (newValue: S) => void },
+  InjectionKey<GenericStore>
+] {
   const pinia = getActivePinia()
   pinia.state.value[$id] = initialState || buildState()
   // const state: Ref<S> = toRef(_p.state.value, $id)
@@ -172,6 +179,13 @@ function initStore<Id extends string, S extends StateTree>(
     $reset,
   } as StoreWithState<Id, S>
 
+  const injectionSymbol = __DEV__ ? Symbol(`PiniaStore(${$id})`) : Symbol()
+
+  // avoid warnings with injections not found
+  if (pinia._a) {
+    pinia._a.provide(injectionSymbol, null)
+  }
+
   return [
     storeWithState,
     {
@@ -182,6 +196,7 @@ function initStore<Id extends string, S extends StateTree>(
         isListening = true
       },
     },
+    injectionSymbol,
   ]
 }
 
@@ -271,8 +286,11 @@ export function defineStore<
   const { id, state, getters, actions } = options
 
   function useStore(pinia?: Pinia | null): Store<Id, S, G, A> {
+    const hasInstance = getCurrentInstance()
+    // only run provide when pinia hasn't been manually passed
+    const shouldProvide = hasInstance && !pinia
     // avoid injecting if `useStore` when not possible
-    pinia = pinia || (getCurrentInstance() && inject(piniaSymbol))
+    pinia = pinia || (hasInstance && inject(piniaSymbol))
     if (pinia) setActivePinia(pinia)
     // TODO: worth warning on server if no piniaKey as it can leak data
     pinia = getActivePinia()
@@ -280,7 +298,11 @@ export function defineStore<
     if (!stores) storesMap.set(pinia, (stores = new Map()))
 
     let storeAndDescriptor = stores.get(id) as
-      | [StoreWithState<Id, S>, StateDescriptor<S>]
+      | [
+          StoreWithState<Id, S>,
+          StateDescriptor<S>,
+          InjectionKey<Store<Id, S, G, A>>
+        ]
       | undefined
     if (!storeAndDescriptor) {
       storeAndDescriptor = initStore(id, state, pinia.state.value[id])
@@ -297,6 +319,12 @@ export function defineStore<
         options
       )
 
+      // allow children to reuse this store instance to avoid creating a new
+      // store for each child
+      if (shouldProvide) {
+        provide(storeAndDescriptor[2], store)
+      }
+
       if (
         IS_CLIENT &&
         __BROWSER__ &&
@@ -321,14 +349,18 @@ export function defineStore<
       return store
     }
 
-    return buildStoreToUse(
-      storeAndDescriptor[0],
-      storeAndDescriptor[1],
-      id,
-      getters as Record<string, Method> | undefined,
-      actions as Record<string, Method> | undefined,
-      // @ts-ignore: because we don't have extend on G and A
-      options
+    return (
+      // null avoids the warning for not found injection key
+      (hasInstance && inject(storeAndDescriptor[2], null)) ||
+      buildStoreToUse(
+        storeAndDescriptor[0],
+        storeAndDescriptor[1],
+        id,
+        getters as Record<string, Method> | undefined,
+        actions as Record<string, Method> | undefined,
+        // @ts-ignore: because we don't have extend on G and A
+        options
+      )
     )
   }