const main = useMainStore()
router.beforeEach((to, from, next) => {
- if (main.state.isLoggedIn) next()
+ if (main.$state.isLoggedIn) next()
else next('/login')
})
```
// ✅ 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')
})
```
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
},
setFoo(foo: string) {
- this.patch({ nested: { foo } })
+ this.$patch({ nested: { foo } })
},
combined() {
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', () => {
// 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', () => {
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')
})
})
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
}
async login(user: string, password: string) {
const userData = await apiLogin(user, password)
- this.patch({
+ this.$patch({
name: user,
...userData,
})
logout() {
this.login('a', 'b').then(() => {})
- this.patch({
+ this.$patch({
name: '',
isAdmin: false,
})
store.login('e', 'e').then(() => {})
- store.patch({
+ store.$patch({
name: '',
isAdmin: false,
})
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,
}
},
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',
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',
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',
it('sets the initial state', () => {
const store = useStore()
- expect(store.state).toEqual({
+ expect(store.$state).toEqual({
a: true,
nested: {
foo: 'foo',
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',
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', () => {
const store = useStore()
- expect(store.state).toEqual({
+ expect(store.$state).toEqual({
a: false,
nested: {
foo: 'bar',
it('can replace its state', () => {
const store = useStore()
- store.state = {
+ store.$state = {
a: false,
nested: {
foo: 'bar',
},
},
}
- expect(store.state).toEqual({
+ expect(store.$state).toEqual({
a: false,
nested: {
foo: 'bar',
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(
{
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(
{
storeName: 'main',
type: expect.stringContaining('patch'),
},
- store.state
+ store.$state
)
})
})
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)
})
const store = useStore()
-expectType<{ a: 'on' | 'off' }>(store.state)
+expectType<{ a: 'on' | 'off' }>(store.$state)
expectType<{ upper: string }>(store)
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',
{
// 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
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)
})
}
},
}
)
- 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)
}
}
- 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>
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>
-import { Ref } from '@vue/composition-api'
-
export type StateTree = Record<string | number | symbol, any>
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]> }
/**
* 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
/**
* 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