]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
refactor: prefix store public properties with `$`
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 9 Feb 2021 17:49:15 +0000 (18:49 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Tue, 9 Feb 2021 17:49:15 +0000 (18:49 +0100)
Close #253. This also aligns with the current v2 API and makes migration
easier from Vue 2 to Vue 3.

BREAKING CHANGES: all store properties (`id`, `state`, `patch`, `subscribe`, and `reset`) are now prefixed with `$` to allow properties defined with the same type and avoid types breaking. Tip: you can refactor your whole codebase with F2 (or right-click + Rename) on each of the store's properties

13 files changed:
README.md
__tests__/actions.spec.ts
__tests__/pinia/stores/cart.ts
__tests__/pinia/stores/user.ts
__tests__/ssr/app/App.ts
__tests__/store.patch.spec.ts
__tests__/store.spec.ts
__tests__/subscriptions.spec.ts
__tests__/tds/store.test-d.ts
src/devtools.ts
src/rootStore.ts
src/store.ts
src/types.ts

index 9fdf7c730d22e1be3104232b5fc52d5840d637ae..9fef74ee73dc093f41e60ae25210e04983d344bc 100644 (file)
--- a/README.md
+++ b/README.md
@@ -168,7 +168,7 @@ const router = new Router({
 const main = useMainStore()
 
 router.beforeEach((to, from, next) => {
-  if (main.state.isLoggedIn) next()
+  if (main.$state.isLoggedIn) next()
   else next('/login')
 })
 ```
@@ -191,7 +191,7 @@ router.beforeEach((to, from, next) => {
   // ✅ This will work (requires an extra param for SSR, see below)
   const main = useMainStore()
 
-  if (main.state.isLoggedIn) next()
+  if (main.$state.isLoggedIn) next()
   else next('/login')
 })
 ```
@@ -246,23 +246,23 @@ To mutate the state you can either directly change something:
 main.counter++
 ```
 
-or call the method `patch` that allows you apply multiple changes at the same time with a partial `state` object:
+or call the method `$patch` that allows you apply multiple changes at the same time with a partial `state` object:
 
 ```ts
-main.patch({
+main.$patch({
   counter: -1,
   name: 'Abalam',
 })
 ```
 
-The main difference here is that `patch` allows you to group multiple changes into one single entry in the devtools.
+The main difference here is that `$patch` allows you to group multiple changes into one single entry in the devtools.
 
 ### Replacing the `state`
 
 Simply set it to a new object;
 
 ```ts
-main.state = { counter: 666, name: 'Paimon' }
+main.$state = { counter: 666, name: 'Paimon' }
 ```
 
 ### SSR
index a97a7227c3d7a6cd786b8513256669c21ccd5eed..f1fdd32478714237ac3360dca62aba888a4c529d 100644 (file)
@@ -30,7 +30,7 @@ describe('Actions', () => {
         },
 
         setFoo(foo: string) {
-          this.patch({ nested: { foo } })
+          this.$patch({ nested: { foo } })
         },
 
         combined() {
@@ -52,34 +52,34 @@ describe('Actions', () => {
     actions: {
       swap() {
         const bStore = useB()
-        const b = bStore.state.b
-        bStore.state.b = this.state.a
-        this.state.a = b
+        const b = bStore.$state.b
+        bStore.$state.b = this.$state.a
+        this.$state.a = b
       },
     },
   })
 
   it('can use the store as this', () => {
     const store = useStore()
-    expect(store.state.a).toBe(true)
+    expect(store.$state.a).toBe(true)
     store.toggle()
-    expect(store.state.a).toBe(false)
+    expect(store.$state.a).toBe(false)
   })
 
   it('store is forced as the context', () => {
     const store = useStore()
-    expect(store.state.a).toBe(true)
+    expect(store.$state.a).toBe(true)
     store.toggle.call(null)
-    expect(store.state.a).toBe(false)
+    expect(store.$state.a).toBe(false)
   })
 
   it('can call other actions', () => {
     const store = useStore()
-    expect(store.state.a).toBe(true)
-    expect(store.state.nested.foo).toBe('foo')
+    expect(store.$state.a).toBe(true)
+    expect(store.$state.nested.foo).toBe('foo')
     store.combined()
-    expect(store.state.a).toBe(false)
-    expect(store.state.nested.foo).toBe('bar')
+    expect(store.$state.a).toBe(false)
+    expect(store.$state.nested.foo).toBe('bar')
   })
 
   it('supports being called between requests', () => {
@@ -91,12 +91,12 @@ describe('Actions', () => {
     // simulate a different request
     setActiveReq(req2)
     const bStore = useB()
-    bStore.state.b = 'c'
+    bStore.$state.b = 'c'
 
     aStore.swap()
-    expect(aStore.state.a).toBe('b')
+    expect(aStore.$state.a).toBe('b')
     // a different instance of b store was used
-    expect(bStore.state.b).toBe('c')
+    expect(bStore.$state.b).toBe('c')
   })
 
   it('can force the req', () => {
@@ -105,13 +105,13 @@ describe('Actions', () => {
     const aStore = useA(req1)
 
     let bStore = useB(req2)
-    bStore.state.b = 'c'
+    bStore.$state.b = 'c'
 
     aStore.swap()
-    expect(aStore.state.a).toBe('b')
+    expect(aStore.$state.a).toBe('b')
     // a different instance of b store was used
-    expect(bStore.state.b).toBe('c')
+    expect(bStore.$state.b).toBe('c')
     bStore = useB(req1)
-    expect(bStore.state.b).toBe('a')
+    expect(bStore.$state.b).toBe('a')
   })
 })
index f2b3480bcdfb8c6bb85251fcf1779a167d546cf9..63eb26ca8be8a5d16fc9153f41e0dec30069a09d 100644 (file)
@@ -38,23 +38,23 @@ export type CartStore = ReturnType<typeof useCartStore>
 
 export function addItem(name: string) {
   const store = useCartStore()
-  store.state.rawItems.push(name)
+  store.$state.rawItems.push(name)
 }
 
 export function removeItem(name: string) {
   const store = useCartStore()
-  const i = store.state.rawItems.indexOf(name)
-  if (i > -1) store.state.rawItems.splice(i, 1)
+  const i = store.$state.rawItems.indexOf(name)
+  if (i > -1) store.$state.rawItems.splice(i, 1)
 }
 
 export async function purchaseItems() {
   const cart = useCartStore()
   const user = useUserStore()
-  if (!user.state.name) return
+  if (!user.$state.name) return
 
   console.log('Purchasing', cart.items.value)
   const n = cart.items.value.length
-  cart.state.rawItems = []
+  cart.$state.rawItems = []
 
   return n
 }
index 33f15fdf33c95486077145820058b34a2b840750..28b250d5c9b34beab529dd7ed32388dda0defc94 100644 (file)
@@ -15,7 +15,7 @@ export const useUserStore = defineStore({
     async login(user: string, password: string) {
       const userData = await apiLogin(user, password)
 
-      this.patch({
+      this.$patch({
         name: user,
         ...userData,
       })
@@ -24,7 +24,7 @@ export const useUserStore = defineStore({
     logout() {
       this.login('a', 'b').then(() => {})
 
-      this.patch({
+      this.$patch({
         name: '',
         isAdmin: false,
       })
@@ -44,7 +44,7 @@ export function logout() {
 
   store.login('e', 'e').then(() => {})
 
-  store.patch({
+  store.$patch({
     name: '',
     isAdmin: false,
   })
index 5699a4d2ef30b0217fc8d5f17eb6c93182e23004..d4fbfc04e5c43af87446ed0d1c71f2388866c69f 100644 (file)
@@ -4,21 +4,21 @@ import { useStore } from './store'
 export default defineComponent({
   async serverPrefetch() {
     const store = useStore()
-    store.state.counter++
+    store.$state.counter++
   },
 
   setup() {
     const store = useStore()
 
-    const doubleCount = computed(() => store.state.counter * 2)
+    const doubleCount = computed(() => store.$state.counter * 2)
     function increment() {
-      store.state.counter++
+      store.$state.counter++
     }
 
     return {
       doubleCount,
       increment,
-      state: store.state,
+      state: store.$state,
     }
   },
 
index a8927f38bd12ae3c05fb701621cb93ec1855e255..25b68b9f6b069abc0939d628de28754f1ab3d1ff 100644 (file)
@@ -18,8 +18,8 @@ describe('store.patch', () => {
 
   it('patches a property without touching the rest', () => {
     const store = useStore()
-    store.patch({ a: false })
-    expect(store.state).toEqual({
+    store.$patch({ a: false })
+    expect(store.$state).toEqual({
       a: false,
       nested: {
         foo: 'foo',
@@ -30,16 +30,16 @@ describe('store.patch', () => {
 
   it('patches a nested property without touching the rest', () => {
     const store = useStore()
-    store.patch({ nested: { foo: 'bar' } })
-    expect(store.state).toEqual({
+    store.$patch({ nested: { foo: 'bar' } })
+    expect(store.$state).toEqual({
       a: true,
       nested: {
         foo: 'bar',
         a: { b: 'string' },
       },
     })
-    store.patch({ nested: { a: { b: 'hello' } } })
-    expect(store.state).toEqual({
+    store.$patch({ nested: { a: { b: 'hello' } } })
+    expect(store.$state).toEqual({
       a: true,
       nested: {
         foo: 'bar',
@@ -50,8 +50,8 @@ describe('store.patch', () => {
 
   it('patches multiple properties at the same time', () => {
     const store = useStore()
-    store.patch({ a: false, nested: { foo: 'hello' } })
-    expect(store.state).toEqual({
+    store.$patch({ a: false, nested: { foo: 'hello' } })
+    expect(store.$state).toEqual({
       a: false,
       nested: {
         foo: 'hello',
index 9b1b0367c3d802dd0328902ca31152b4d1b6c473..a3dbc301b600d4ae785bc40741345c57b4483719 100644 (file)
@@ -20,7 +20,7 @@ describe('Store', () => {
 
   it('sets the initial state', () => {
     const store = useStore()
-    expect(store.state).toEqual({
+    expect(store.$state).toEqual({
       a: true,
       nested: {
         foo: 'foo',
@@ -31,13 +31,13 @@ describe('Store', () => {
 
   it('can be reset', () => {
     const store = useStore()
-    store.state.a = false
+    store.$state.a = false
     const spy = jest.fn()
-    store.subscribe(spy)
-    store.reset()
-    store.state.nested.foo = 'bar'
+    store.$subscribe(spy)
+    store.$reset()
+    store.$state.nested.foo = 'bar'
     expect(spy).not.toHaveBeenCalled()
-    expect(store.state).toEqual({
+    expect(store.$state).toEqual({
       a: true,
       nested: {
         foo: 'bar',
@@ -49,7 +49,7 @@ describe('Store', () => {
   it('can create an empty state if no state option is provided', () => {
     const store = defineStore({ id: 'some' })()
 
-    expect(store.state).toEqual({})
+    expect(store.$state).toEqual({})
   })
 
   it('can hydrate the state', () => {
@@ -77,7 +77,7 @@ describe('Store', () => {
 
     const store = useStore()
 
-    expect(store.state).toEqual({
+    expect(store.$state).toEqual({
       a: false,
       nested: {
         foo: 'bar',
@@ -88,7 +88,7 @@ describe('Store', () => {
 
   it('can replace its state', () => {
     const store = useStore()
-    store.state = {
+    store.$state = {
       a: false,
       nested: {
         foo: 'bar',
@@ -97,7 +97,7 @@ describe('Store', () => {
         },
       },
     }
-    expect(store.state).toEqual({
+    expect(store.$state).toEqual({
       a: false,
       nested: {
         foo: 'bar',
@@ -109,17 +109,17 @@ describe('Store', () => {
   it('do not share the state between same id store', () => {
     const store = useStore()
     const store2 = useStore()
-    expect(store.state).not.toBe(store2.state)
-    store.state.nested.a.b = 'hey'
-    expect(store2.state.nested.a.b).toBe('string')
+    expect(store.$state).not.toBe(store2.$state)
+    store.$state.nested.a.b = 'hey'
+    expect(store2.$state.nested.a.b).toBe('string')
   })
 
   it('subscribe to changes', () => {
     const store = useStore()
     const spy = jest.fn()
-    store.subscribe(spy)
+    store.$subscribe(spy)
 
-    store.state.a = false
+    store.$state.a = false
 
     expect(spy).toHaveBeenCalledWith(
       {
@@ -127,17 +127,17 @@ describe('Store', () => {
         storeName: 'main',
         type: expect.stringContaining('in place'),
       },
-      store.state
+      store.$state
     )
   })
 
   it('subscribe to changes done via patch', () => {
     const store = useStore()
     const spy = jest.fn()
-    store.subscribe(spy)
+    store.$subscribe(spy)
 
     const patch = { a: false }
-    store.patch(patch)
+    store.$patch(patch)
 
     expect(spy).toHaveBeenCalledWith(
       {
@@ -145,7 +145,7 @@ describe('Store', () => {
         storeName: 'main',
         type: expect.stringContaining('patch'),
       },
-      store.state
+      store.$state
     )
   })
 })
index 3b426c69fb9d639845afc3acccaa35f3cce31410..859f3cc29473f08102a4dd18a88d0c40592cafa1 100644 (file)
@@ -19,27 +19,27 @@ describe('Subscriptions', () => {
 
   it('fires callback when patch is applied', () => {
     const spy = jest.fn()
-    store.subscribe(spy)
-    store.state.name = 'Cleiton'
+    store.$subscribe(spy)
+    store.$state.name = 'Cleiton'
     expect(spy).toHaveBeenCalledTimes(1)
   })
 
   it('unsubscribes callback when unsubscribe is called', () => {
     const spy = jest.fn()
-    const unsubscribe = store.subscribe(spy)
+    const unsubscribe = store.$subscribe(spy)
     unsubscribe()
-    store.state.name = 'Cleiton'
+    store.$state.name = 'Cleiton'
     expect(spy).not.toHaveBeenCalled()
   })
 
   it('listeners are not affected when unsubscribe is called multiple times', () => {
     const func1 = jest.fn()
     const func2 = jest.fn()
-    const unsubscribe1 = store.subscribe(func1)
-    store.subscribe(func2)
+    const unsubscribe1 = store.$subscribe(func1)
+    store.$subscribe(func2)
     unsubscribe1()
     unsubscribe1()
-    store.state.name = 'Cleiton'
+    store.$state.name = 'Cleiton'
     expect(func1).not.toHaveBeenCalled()
     expect(func2).toHaveBeenCalledTimes(1)
   })
index ebee55bac485059a0dc367d2eea747342eb21c0a..67db2a6bad5f02a95bc212fc9f16085620e649b0 100644 (file)
@@ -13,7 +13,7 @@ const useStore = defineStore({
 
 const store = useStore()
 
-expectType<{ a: 'on' | 'off' }>(store.state)
+expectType<{ a: 'on' | 'off' }>(store.$state)
 
 expectType<{ upper: string }>(store)
 
index afddc24547eb9beccc2af7d1bc70fd3bfd7d1153..4be13c2aa8df59aeaf9ad22c6479dbb03f2f7c4c 100644 (file)
@@ -57,26 +57,26 @@ export function useStoreDevtools(store: StoreWithState<string, StateTree>) {
     devtoolHook.emit('vuex:init', rootStore)
   }
 
-  rootStore.state[store.id] = store.state
+  rootStore.state[store.$id] = store.$state
 
   // tell the devtools we added a module
-  rootStore.registerModule(store.id, store)
+  rootStore.registerModule(store.$id, store)
 
-  Object.defineProperty(rootStore.state, store.id, {
-    get: () => store.state,
-    set: (state) => (store.state = state),
+  Object.defineProperty(rootStore.state, store.$id, {
+    get: () => store.$state,
+    set: (state) => (store.$state = state),
   })
 
   // Vue.set(rootStore.state, store.name, store.state)
   // the trailing slash is removed by the devtools
-  rootStore._modulesNamespaceMap[store.id + '/'] = true
+  rootStore._modulesNamespaceMap[store.$id + '/'] = true
 
   devtoolHook.on('vuex:travel-to-state', (targetState) => {
-    store.state = targetState[store.id]
+    store.$state = targetState[store.$id]
   })
 
-  store.subscribe((mutation, state) => {
-    rootStore.state[store.id] = state
+  store.$subscribe((mutation, state) => {
+    rootStore.state[store.$id] = state
     devtoolHook.emit(
       'vuex:mutation',
       {
index 85a23c69901ca7c1466ec0390e03f02d6214beb0..09ed006c99c4693a5a33239ae8dea2ed15707ef5 100644 (file)
@@ -50,7 +50,7 @@ export function getRootState(req: NonNullObject): Record<string, StateTree> {
 
   // forEach is the only one that also works on IE11
   stores.forEach((store) => {
-    rootState[store.id] = store.state
+    rootState[store.$id] = store.$state
   })
 
   return rootState
index 78708e4521905e8912ce253c637c34ba404230fc..7ecc7fff1e1f68788cbb8f5d72bf75ac74f58438 100644 (file)
@@ -66,24 +66,24 @@ export function buildStore<
   G extends Record<string, Method>,
   A extends Record<string, Method>
 >(
-  id: Id,
+  $id: Id,
   buildState = () => ({} as S),
   getters: G = {} as G,
   actions: A = {} as A,
   initialState?: S | undefined
 ): Store<Id, S, G, A> {
-  const state: Ref<S> = ref(initialState || buildState())
+  const $state: Ref<S> = ref(initialState || buildState())
   const _r = getActiveReq()
 
   let isListening = true
   let subscriptions: SubscriptionCallback<S>[] = []
 
   watch(
-    () => state.value,
+    () => $state.value,
     (state) => {
       if (isListening) {
         subscriptions.forEach((callback) => {
-          callback({ storeName: id, type: '🧩 in place', payload: {} }, state)
+          callback({ storeName: $id, type: '🧩 in place', payload: {} }, state)
         })
       }
     },
@@ -93,20 +93,20 @@ export function buildStore<
     }
   )
 
-  function patch(partialState: DeepPartial<S>): void {
+  function $patch(partialState: DeepPartial<S>): void {
     isListening = false
-    innerPatch(state.value, partialState)
+    innerPatch($state.value, partialState)
     isListening = true
     // because we paused the watcher, we need to manually call the subscriptions
     subscriptions.forEach((callback) => {
       callback(
-        { storeName: id, type: '⤵️ patch', payload: partialState },
-        state.value
+        { storeName: $id, type: '⤵️ patch', payload: partialState },
+        $state.value
       )
     })
   }
 
-  function subscribe(callback: SubscriptionCallback<S>) {
+  function $subscribe(callback: SubscriptionCallback<S>) {
     subscriptions.push(callback)
     return () => {
       const idx = subscriptions.indexOf(callback)
@@ -116,27 +116,27 @@ export function buildStore<
     }
   }
 
-  function reset() {
+  function $reset() {
     subscriptions = []
-    state.value = buildState()
+    $state.value = buildState()
   }
 
   const storeWithState: StoreWithState<Id, S> = {
-    id,
+    $id,
     _r,
     // @ts-ignore, `reactive` unwraps this making it of type S
-    state: computed<S>({
-      get: () => state.value,
+    $state: computed<S>({
+      get: () => $state.value,
       set: (newState) => {
         isListening = false
-        state.value = newState
+        $state.value = newState
         isListening = true
       },
     }),
 
-    patch,
-    subscribe,
-    reset,
+    $patch,
+    $subscribe,
+    $reset,
   }
 
   const computedGetters: StoreWithGetters<G> = {} as StoreWithGetters<G>
@@ -165,7 +165,7 @@ export function buildStore<
   const store: Store<Id, S, G, A> = reactive({
     ...storeWithState,
     // using this means no new properties can be added as state
-    ...toComputed(state),
+    ...toComputed($state),
     ...computedGetters,
     ...wrappedActions,
   }) as Store<Id, S, G, A>
index ede3d79a005ad5925a85dd6206b816d6073b092d..bebea62c830dcd94f4716516aa4509f31e918cf8 100644 (file)
@@ -1,5 +1,3 @@
-import { Ref } from '@vue/composition-api'
-
 export type StateTree = Record<string | number | symbol, any>
 
 export function isPlainObject(
@@ -16,8 +14,6 @@ export function isPlainObject(
 
 export type NonNullObject = Record<any, any>
 
-type TODO = any
-// type StoreMethod = TODO
 export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
 // type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
 
@@ -30,12 +26,12 @@ export interface StoreWithState<Id extends string, S extends StateTree> {
   /**
    * Unique identifier of the store
    */
-  id: Id
+  $id: Id
 
   /**
    * State of the Store. Setting it will replace the whole state.
    */
-  state: S
+  $state: S
 
   /**
    * Private property defining the request key for this store
@@ -44,22 +40,22 @@ export interface StoreWithState<Id extends string, S extends StateTree> {
 
   /**
    * Applies a state patch to current state. Allows passing nested values
-   * @param partialState patch to apply to the state
+   * @param partialState patch to apply to the state
    */
-  patch(partialState: DeepPartial<S>): void
+  $patch(partialState: DeepPartial<S>): void
 
   /**
    * Resets the store to its initial state by removing all subscriptions and
    * building a new state object
    */
-  reset(): void
+  $reset(): void
 
   /**
    * Setups a callback to be called whenever the state changes.
-   * @param callback callback that is called whenever the state
+   * @param callback callback that is called whenever the state
    * @returns function that removes callback from subscriptions
    */
-  subscribe(callback: SubscriptionCallback<S>): () => void
+  $subscribe(callback: SubscriptionCallback<S>): () => void
 }
 
 export type Method = (...args: any[]) => any