SlideShare a Scribd company logo
1 of 59
Download to read offline
Vuex to Pinia
How to migrate an existing app from Vuex to Pinia?
- Denny Biasiolli -
1
WHO AM I
Denny Biasiolli
Full Stack Developer
(JavaScript, Python, Go)
Front End Developer UX/ UI
Fingerprint Supervision Ltd
Italy, Savigliano (CN)
@dennybiasiolli
denny.biasiolli@gmail.com
www.dennybiasiolli.com
2
WHAT IS PINIA?
Vuex 5 Pinia is the officially recognized
state management library for Vue.
Started as an experiment to redesign what a Store for
Vue could look like with the Composition API
https://pinia.vuejs.org/introduction.html
3
WHAT IS 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.
Pinia already implemented most of what they wanted
in Vuex 5 and decided to make it the new
recommendation instead.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
4
COMPARISON WITH VUEX
5
(SMALL) COMPARISON WITH VUEX
3.X/4.X
Pinia works with Vue 2 and Vue 3
Simpler API than Vuex
Mutations no longer exist, often perceived as
extremely verbose.
No need to create custom complex wrappers to
support TypeScript
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
6
(SMALL) COMPARISON WITH VUEX
3.X/4.X
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.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
7
(SMALL) COMPARISON WITH VUEX
3.X/4.X
No more nested structuring of modules.
You can still use a store inside another.
No namespaced modules.
You could say all stores are namespaced.
https://pinia.vuejs.org/introduction.html#comparison-with-vuex
8
INSTALL PINIA
Pinia can coexist with Vuex
Vue 3.x and 2.7.x
Vue 2.6.x
npm install -S pinia
npm install -S @vue/composition-api
npm install -S pinia
https://pinia.vuejs.org/getting-started.html#installation
9
ROOT STORE (BASIC)
Vue 3.x
Vue 2.x
// src/main.js
import { createPinia } from 'pinia'
app.use(createPinia())
// src/main.js
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
// other options (store, render function, etc)...
pinia,
})
10
ROOT STORE (ADVANCED)
Vue 3.x
Vue 2.x
// src/stores/index.js
import { createPinia } from 'pinia'
export default createPinia()
// src/stores/index.js
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
Vue.use(PiniaVuePlugin)
export default createPinia()
11
ROOT STORE (ADVANCED)
Vue 3.x
Vue 2.x
// src/main.js
import pinia from './stores'
app.use(pinia)
// src/main.js
import pinia from './stores'
new Vue({
// other options (store, render function, etc)...
pinia,
})
12
DEFINING A STORE
Vue 3.x/2.x
// src/stores/main.js
import { defineStore } from 'pinia'
// the first argument is a unique id of the store
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
isEven: state => state.count % 2 === 0,
isOdd() {
return !this.isEven
},
},
13
DEFINING A STORE
Vue 3.x/2.x
// src/stores/main.js
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
// ..
actions: {
increment() {
this.counter++
},
},
})
14
USING THE STORE
Vue 3.x
// src/components/Component.js
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useStore } from '@/stores/main'
const store = useStore() // direct usage of state/actions
const count = computed(() => store.count)
const isEven = computed(() => store.isEven)
const isOdd = computed(() => store.isOdd)
// or
const { count, isEven, isOdd } = storeToRefs(store)
// actions
const { increment } = store
15
USING THE STORE
Vue 2.x
// src/components/Component.js
import { mapState, mapActions } from 'pinia'
import { useStore } from '@/stores/main'
// computed section
...mapState(useStore, ['count', 'isEven', 'isOdd']),
...mapState(useStore, {
count: store => store.count,
isEven: store => store.isEven,
isOdd: store => store.isOdd,
}),
// methods section
...mapActions(useStore, ['increment']),
...mapActions(useStore, {
increment: 'increment',
}),
16
17
PREPARING THE MIGRATION
Vuex store structure
src
└── store
├── index.js # Vuex init, imports modules, main store
└── modules
├── todo.js # 'todo' namespace
└── todo-types.js
18
Vuex store definition
// src/store/index.js
export const defaultState = { /* ... */ }
export const getters = { /* ... */ }
export const mutations = { /* ... */ }
export const actions = { /* ... */ }
export const modules = { /* ... */ }
export default new Vuex.Store({
state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x
// or
state: { ...defaultState }, // Vue 2.x + Vuex 3.x
getters, mutations, actions, modules,
})
19
Vuex store module
// src/store/modules/todo.js
export const defaultState = { /* ... */ }
export const getters = { /* ... */ }
export const mutations = { /* ... */ }
export const actions = { /* ... */ }
export default {
state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x
// or
state: { ...defaultState }, // Vue 2.x + Vuex 3.x
getters, mutations, actions,
namespaced: true,
}
20
MIGRATE STORE DEFINITION
--- a/src/store/index.js
+++ b/src/stores/main.js
-import { createStore } from 'vuex'
+import { defineStore } from 'pinia'
-export default createStore({
+export const useStore = defineStore('main', {
- state: () => ({ ...defaultState }),
+ state: () => defaultState,
getters,
- mutations,
actions,
- modules,
})
21
MIGRATE STORE GETTERS
State? No changes!
--- a/src/store/index.js
+++ b/src/stores/main.js
export const getters = {
isEven: state => state.count % 2 === 0,
- isOdd(state, getters) {
- return !getters.isEven
+ isOdd() {
+ return !this.isEven
},
}
22
MIGRATE STORE MUTATIONS
mutations → actions
--- a/src/store/index.js
+++ b/src/stores/main.js
-export const mutations = {
+export const actions = {
- increment(state, payload) {
+ increment(payload) { // or multiple args
- state.count++
+ this.count++
},
}
23
MIGRATE STORE ACTIONS
--- a/src/store/index.js
+++ b/src/stores/main.js
export const actions = {
- incrementAsync({ commit, dispatch, state }, payload) {
+ incrementAsync(payload) { // or multiple args
setTimeout(() => {
+ // use `this` instead of `commit, dispatch, state`
- commit('increment')
+ this.increment()
}, 1000)
},
}
24
MAIN STORE MIGRATION
25
TODO MODULE/STORE MIGRATION
26
WHAT ABOUT TESTS?
27
TEST MIGRATION
state: no changes
getters
use .call on a getter when using this to access
other getters
-expect(getters.isOdd({}, { isEven: false })).toBe(true)
+expect(getters.isOdd.call({ isEven: false })).toBe(true)
28
TEST MIGRATION
mutations: → actions
actions
use .call to pass state/getters/actions
-const context = { commit: vi.fn() }
-actions.incrementAsync(context)
+const state = { increment: vi.fn() }
+actions.incrementAsync.call(state)
// ...
-expect(context.commit).toHaveBeenCalled()
+expect(state.increment).toHaveBeenCalled()
29
STATE TEST MIGRATION
import { test, expect } from 'vitest'
import { defaultState } from '@/stores/main'
test('defaultState should be as expected', () => {
expect(defaultState).toEqual({
count: 0,
})
})
30
GETTERS TEST MIGRATION
import { test, expect } from 'vitest'
import { getters } from '@/stores/main'
test('isEven should work as expected', () => {
expect(getters.isEven({ count: 0 })).toBe(true)
expect(getters.isEven({ count: 1 })).toBe(false)
})
test('isOdd should work as expected', () => {
- expect(getters.isOdd(undefined, { isEven: true }))
+ expect(getters.isOdd.call({ isEven: true }))
.toBe(false)
- expect(getters.isOdd(undefined, { isEven: false }))
+ expect(getters.isOdd.call({ isEven: false }))
.toBe(true)
})
31
MUTATIONS TEST MIGRATION
import { test, expect } from 'vitest'
-import { mutations } from '@/store/index'
+import { actions } from '@/stores/main'
test('increment should work as expected', () => {
const state = { count: 0 }
- mutations.increment(state)
+ actions.increment.call(state)
expect(state.count).toBe(1)
- mutations.increment(state)
+ actions.increment.call(state)
expect(state.count).toBe(2)
})
32
ACTIONS TEST MIGRATION
import { test, expect, vi } from 'vitest'
-import { actions } from '@/store/index'
+import { actions } from '@/stores/main'
test('incrementAsync should work as expected', () => {
vi.useFakeTimers()
- const context = { commit: vi.fn() }
- actions.incrementAsync(context)
+ const state = { increment: vi.fn() }
+ actions.incrementAsync.call(state)
- expect(context.commit).not.toHaveBeenCalled()
+ expect(state.increment).not.toHaveBeenCalled();
vi.advanceTimersByTime(1000)
- expect(context.commit).toHaveBeenCalledWith('increment')
+ expect(state.increment).toHaveBeenCalled();
vi.useRealTimers()
})
33
COMPONENT MIGRATION (VUE 3)
import { computed } from 'vue'
-import { useStore } from 'vuex'
+import { useStore } from '@/stores/main'
const store = useStore()
-const count = computed(() => store.state.count)
-const isEven = computed(() => store.getters.isEven)
-const isOdd = computed(() => store.getters.isOdd)
+const count = computed(() => store.count)
+const isEven = computed(() => store.isEven)
+const isOdd = computed(() => store.isOdd)
-const increment = () => store.commit('increment')
-const incrementAsync = () => store.dispatch('incrementAsync')
+const { increment, incrementAsync } = store
34
COMPONENT MIGRATION (VUE 3)
-import { computed } from 'vue'
+import { storeToRefs } from 'pinia'
-import { useStore } from 'vuex'
+import { useStore } from '@/stores/main'
const store = useStore()
-const count = computed(() => store.state.count)
-const isEven = computed(() => store.getters.isEven)
-const isOdd = computed(() => store.getters.isOdd)
+const { count, isEven, isOdd } = storeToRefs(store)
-const increment = () => store.commit('increment')
-const incrementAsync = () => store.dispatch('incrementAsync')
+const { increment, incrementAsync } = store
35
COMPONENT MIGRATION (VUE 2)
-import { mapState, mapGetters, mapMutations, mapActions } fro
+import { mapState, mapActions } from 'pinia'
+import { useStore } from '@/stores/main'
computed: {
- ...mapState(['count']),
- ...mapGetters(['isEven', 'isOdd']),
+ ...mapState(useStore, ['count', 'isEven', 'isOdd']),
},
methods: {
- ...mapMutations(['increment']),
- ...mapActions(['incrementAsync']),
+ ...mapActions(useStore, ['increment', 'incrementAsync']),
},
36
COMPONENT MIGRATION (VUE 2)
-import { createNamespacedHelpers } from 'vuex'
-import { GET_TODO_LIST } from '@/store/modules/todo-types'
-const { mapState, mapActions } = createNamespacedHelpers('tod
+import { mapState, mapActions } from 'pinia'
+import { useTodoStore } from '@/stores/todo'
computed: {
- ...mapState(['todoList']),
+ ...mapState(useTodoStore, ['todoList']),
},
mounted() {
- this[GET_TODO_LIST]()
+ this.getTodoList()
},
methods: {
- ...mapActions([GET_TODO_LIST]),
+ ...mapActions(useTodoStore, ['getTodoList']),
},
37
COMPONENT TESTING
@pinia/testing
npm install -D @pinia/testing
import { createTestingPinia } from '@pinia/testing'
createTestingPinia({
createSpy: vi.fn,
initialState: { main: { count: 0 } },
})
38
createTestingPinia
automatically mocks all actions
unit test store and components separately
allows you to overwrite getter values in tests
(not working with Vue 2 and Jest)
39
COMPONENT TESTING (VUE 3 + VITEST)
import { createTestingPinia } from '@pinia/testing'
import { shallowMount } from '@vue/test-utils';
import { useStore } from '@/stores/main'
let pinia, store
beforeEach(() => {
pinia = createTestingPinia({
createSpy: vi.fn,
initialState: { main: { count: 0 } },
})
// init store only after creating a testing pinia
store = useStore()
})
// tests
const wrapper = shallowMount(Component, {
global: { plugins: [pinia] }
})
40
COMPONENT TESTING (VUE 2 + JEST)
import { PiniaVuePlugin } from 'pinia'
import { createTestingPinia } from '@pinia/testing'
import { shallowMount, createLocalVue } from '@vue/test-utils'
import { useStore } from '@/stores/main'
const localVue = createLocalVue()
localVue.use(PiniaVuePlugin)
let pinia, store
beforeEach(() => {
pinia = createTestingPinia({
initialState: { main: { count: 0 } },
})
// init store only after creating a testing pinia
store = useStore()
})
// tests
const wrapper = shallowMount(Component, { localVue, pinia })
41
COMPONENT TESTING (VUE 2 + JEST)
Getters are not writable, you need to set the correct
state in order to make them work as expected.
store.count = 1
// or
store.$patch({
count: 1,
// other properties
})
42
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
+import { createTestingPinia } from '@pinia/testing'
import { describe, test, beforeEach, expect, vi } from 'vites
-import { createStore } from 'vuex'
import { shallowMount } from '@vue/test-utils';
import Counter from '@/components/Counter.vue'
+import { useStore } from '@/stores/main'
43
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
-let store
+let pinia, store
-let mutations = { increment: vi.fn() }
-let actions = { incrementAsync: vi.fn() }
beforeEach(() => {
- store = createStore({
- state: () => ({ count: 0 }),
- getters: { isEven: () => true, isOdd: () => false },
- mutations,
- actions,
- })
+ pinia = createTestingPinia({
+ createSpy: vi.fn,
+ initialState: { main: { count: 0 } },
+ })
+ store = useStore()
})
44
COMPONENT TEST MIGRATION (VUE 3 + VITEST)
test('should respect the snapshot', () => {
const wrapper = shallowMount(Counter, {
- global: { plugins: [store] }
+ global: { plugins: [pinia] }
})
expect(wrapper.element).toMatchSnapshot()
wrapper.findAll('button')[0].trigger('click')
- expect(mutations.increment).toHaveBeenCalled()
+ expect(store.increment).toHaveBeenCalled()
wrapper.findAll('button')[1].trigger('click')
- expect(actions.incrementAsync).toHaveBeenCalled()
+ expect(store.incrementAsync).toHaveBeenCalled()
})
45
COMPONENT TEST MIGRATION (VUE 2 + JEST)
-import Vuex from 'vuex'
+import { PiniaVuePlugin } from 'pinia'
+import { createTestingPinia } from '@pinia/testing'
import { shallowMount, createLocalVue } from '@vue/test-utils
import Counter from '@/components/Counter.vue'
+import { useStore } from '@/stores/main'
const localVue = createLocalVue()
-localVue.use(Vuex)
+localVue.use(PiniaVuePlugin)
46
COMPONENT TEST MIGRATION (VUE 2 + JEST)
-let store
+let pinia, store
-let mutations = { increment: jest.fn() }
-let actions = { incrementAsync: jest.fn() }
beforeEach(() => {
- store = createStore({
- state: { count: 0 },
- getters: { isEven: () => true, isOdd: () => false },
- mutations,
- actions,
- })
+ pinia = createTestingPinia({
+ initialState: { main: { count: 0 } },
+ })
+ store = useStore()
})
47
COMPONENT TEST MIGRATION (VUE 2 + JEST)
test('should respect the snapshot', () => {
const wrapper = shallowMount(Counter, {
- localVue, store,
+ localVue, pinia,
})
expect(wrapper.element).toMatchSnapshot()
wrapper.findAll('button')[0].trigger('click')
- expect(mutations.increment).toHaveBeenCalled()
+ expect(store.increment).toHaveBeenCalled()
wrapper.findAll('button')[1].trigger('click')
- expect(actions.incrementAsync).toHaveBeenCalled()
+ expect(store.incrementAsync).toHaveBeenCalled()
})
48
EVRYTHING SHOULD BE FINE, RIGHT?
49
MIGRATION PROBLEMS NOTES
Direct $store usage
Use the correct store
$store.state.propertyName
$store.state.moduleName.propertyName
$store.getters.getterName
$store.getters['moduleName/getterName']
const myStore = useMyStore()
myStore.propertyName
myStore.getterName
50
MIGRATION PROBLEMS NOTES
Direct $store commit/dispatch
Use store actions
$store.commit.mutationName()
$store.commit.moduleName.mutationName()
$store.dispatch.actionName()
$store.dispatch['moduleName/actionName']()
const myStore = useMyStore()
myStore.actionName()
51
MIGRATION PROBLEMS NOTES
What about a global useStore()?
If used outside of <script setup>,
will give you this error
import { useStore } from '@/stores/main'
const store = useStore()
// other stuff
getActivePinia was called with no active Pinia.
Did you forget to install pinia?
52
MIGRATION PROBLEMS NOTES
Can Vuex and Pinia coexist?
Yes, but...
make sure to migrate entire modules,
not entire components.
53
MIGRATION PROBLEMS NOTES
How to test a real store?
import { setActivePinia, createPinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia())
})
test('should work as expected', () => {
const store = useStore()
// ...
})
54
MIGRATION PROBLEMS NOTES
What about store persistence?
myStore.$subscribe((mutation, state) => {
localStorage.setItem('myStore', JSON.stringify(state))
})
// restore with
myStore.$state = { /* ... */ }
watch(
pinia.state,
(state) => {
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
// restore with
pinia.state.value = { /* ... */ }
https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state
55
FINAL TASKS
1. remove Vuex store from main.js
-import store from './store'
// Vue 3
-app.use(store)
// Vue 2
new Vue({
router,
- store,
pinia,
render: h => h(App),
}).$mount('#app')
56
FINAL TASKS
2. delete Vuex store and tests
3. uninstall Vuex dependencies
rm -rf src/store
rm -rf tests/unit/store
npm uninstall vuex @vue/cli-plugin-vuex
57
2022.VUEDAY.IT
10% discount code
speaker_10OFF
58
LINKS
(moving-to-pinia branch)
@dennybiasiolli
pinia.vuejs.org
github.com/dennybiasiolli/vuex-to-pinia-vue3
github.com/dennybiasiolli/vuex-to-pinia-vue2
www.dennybiasiolli.com
59

More Related Content

Similar to Da Vuex a Pinia: come fare la migrazione

Similar to Da Vuex a Pinia: come fare la migrazione (20)

State manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexState manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to Vuex
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
 
Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?
 
Battle of React State Managers in frontend applications
Battle of React State Managers in frontend applicationsBattle of React State Managers in frontend applications
Battle of React State Managers in frontend applications
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0
 
How to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescriptHow to build to do app using vue composition api and vuex 4 with typescript
How to build to do app using vue composition api and vuex 4 with typescript
 
Vuex
VuexVuex
Vuex
 
React redux
React reduxReact redux
React redux
 
Vue business first
Vue business firstVue business first
Vue business first
 
Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013Yves & Zed @ Developer Conference 2013
Yves & Zed @ Developer Conference 2013
 
Vue js and Dyploma
Vue js and DyplomaVue js and Dyploma
Vue js and Dyploma
 
React lecture
React lectureReact lecture
React lecture
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
 
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018
 
Gutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
 
Building and deploying React applications
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
 
Meet VueJs
Meet VueJsMeet VueJs
Meet VueJs
 
Relay Modern: architecture and workflow
Relay Modern: architecture and workflowRelay Modern: architecture and workflow
Relay Modern: architecture and workflow
 

More from Commit University

More from Commit University (20)

Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)Crea il tuo assistente AI con lo Stregatto (open source python framework)
Crea il tuo assistente AI con lo Stregatto (open source python framework)
 
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdfBreaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
 
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdfAccelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
 
Slide-10years.pdf
Slide-10years.pdfSlide-10years.pdf
Slide-10years.pdf
 
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
 
Vue.js slots.pdf
Vue.js slots.pdfVue.js slots.pdf
Vue.js slots.pdf
 
Commit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptxCommit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptx
 
Sviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PASviluppare da zero una Angular Web App per la PA
Sviluppare da zero una Angular Web App per la PA
 
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
 
Prisma the ORM that node was waiting for
Prisma the ORM that node was waiting forPrisma the ORM that node was waiting for
Prisma the ORM that node was waiting for
 
Decision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit UniversityDecision-making for Software Development Teams - Commit University
Decision-making for Software Development Teams - Commit University
 
Component Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdfComponent Design Pattern nei Game Engine.pdf
Component Design Pattern nei Game Engine.pdf
 
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
Un viaggio alla scoperta dei Language Models e dell’intelligenza artificiale ...
 
Prototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step FunctionsPrototipazione Low-Code con AWS Step Functions
Prototipazione Low-Code con AWS Step Functions
 
KMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and SwiftKMM survival guide: how to tackle struggles between Kotlin and Swift
KMM survival guide: how to tackle struggles between Kotlin and Swift
 
Orchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lcOrchestrare Micro-frontend con micro-lc
Orchestrare Micro-frontend con micro-lc
 
Fastify has defeated Lagacy-Code
Fastify has defeated Lagacy-CodeFastify has defeated Lagacy-Code
Fastify has defeated Lagacy-Code
 
SwiftUI vs UIKit
SwiftUI vs UIKitSwiftUI vs UIKit
SwiftUI vs UIKit
 
Alpine.js: the outsider Javascript framework
Alpine.js: the outsider Javascript frameworkAlpine.js: the outsider Javascript framework
Alpine.js: the outsider Javascript framework
 

Recently uploaded

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Recently uploaded (20)

WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
JohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptxJohnPollard-hybrid-app-RailsConf2024.pptx
JohnPollard-hybrid-app-RailsConf2024.pptx
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
AI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by AnitarajAI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by Anitaraj
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 

Da Vuex a Pinia: come fare la migrazione

  • 1. Vuex to Pinia How to migrate an existing app from Vuex to Pinia? - Denny Biasiolli - 1
  • 2. WHO AM I Denny Biasiolli Full Stack Developer (JavaScript, Python, Go) Front End Developer UX/ UI Fingerprint Supervision Ltd Italy, Savigliano (CN) @dennybiasiolli denny.biasiolli@gmail.com www.dennybiasiolli.com 2
  • 3. WHAT IS PINIA? Vuex 5 Pinia is the officially recognized state management library for Vue. Started as an experiment to redesign what a Store for Vue could look like with the Composition API https://pinia.vuejs.org/introduction.html 3
  • 4. WHAT IS 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. Pinia already implemented most of what they wanted in Vuex 5 and decided to make it the new recommendation instead. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 4
  • 6. (SMALL) COMPARISON WITH VUEX 3.X/4.X Pinia works with Vue 2 and Vue 3 Simpler API than Vuex Mutations no longer exist, often perceived as extremely verbose. No need to create custom complex wrappers to support TypeScript https://pinia.vuejs.org/introduction.html#comparison-with-vuex 6
  • 7. (SMALL) COMPARISON WITH VUEX 3.X/4.X 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. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 7
  • 8. (SMALL) COMPARISON WITH VUEX 3.X/4.X No more nested structuring of modules. You can still use a store inside another. No namespaced modules. You could say all stores are namespaced. https://pinia.vuejs.org/introduction.html#comparison-with-vuex 8
  • 9. INSTALL PINIA Pinia can coexist with Vuex Vue 3.x and 2.7.x Vue 2.6.x npm install -S pinia npm install -S @vue/composition-api npm install -S pinia https://pinia.vuejs.org/getting-started.html#installation 9
  • 10. ROOT STORE (BASIC) Vue 3.x Vue 2.x // src/main.js import { createPinia } from 'pinia' app.use(createPinia()) // src/main.js import { createPinia, PiniaVuePlugin } from 'pinia' Vue.use(PiniaVuePlugin) const pinia = createPinia() new Vue({ // other options (store, render function, etc)... pinia, }) 10
  • 11. ROOT STORE (ADVANCED) Vue 3.x Vue 2.x // src/stores/index.js import { createPinia } from 'pinia' export default createPinia() // src/stores/index.js import { createPinia, PiniaVuePlugin } from 'pinia' import Vue from 'vue' Vue.use(PiniaVuePlugin) export default createPinia() 11
  • 12. ROOT STORE (ADVANCED) Vue 3.x Vue 2.x // src/main.js import pinia from './stores' app.use(pinia) // src/main.js import pinia from './stores' new Vue({ // other options (store, render function, etc)... pinia, }) 12
  • 13. DEFINING A STORE Vue 3.x/2.x // src/stores/main.js import { defineStore } from 'pinia' // the first argument is a unique id of the store export const useStore = defineStore('main', { state: () => ({ count: 0, }), getters: { isEven: state => state.count % 2 === 0, isOdd() { return !this.isEven }, }, 13
  • 14. DEFINING A STORE Vue 3.x/2.x // src/stores/main.js import { defineStore } from 'pinia' export const useStore = defineStore('main', { // .. actions: { increment() { this.counter++ }, }, }) 14
  • 15. USING THE STORE Vue 3.x // src/components/Component.js import { computed } from 'vue' import { storeToRefs } from 'pinia' import { useStore } from '@/stores/main' const store = useStore() // direct usage of state/actions const count = computed(() => store.count) const isEven = computed(() => store.isEven) const isOdd = computed(() => store.isOdd) // or const { count, isEven, isOdd } = storeToRefs(store) // actions const { increment } = store 15
  • 16. USING THE STORE Vue 2.x // src/components/Component.js import { mapState, mapActions } from 'pinia' import { useStore } from '@/stores/main' // computed section ...mapState(useStore, ['count', 'isEven', 'isOdd']), ...mapState(useStore, { count: store => store.count, isEven: store => store.isEven, isOdd: store => store.isOdd, }), // methods section ...mapActions(useStore, ['increment']), ...mapActions(useStore, { increment: 'increment', }), 16
  • 17. 17
  • 18. PREPARING THE MIGRATION Vuex store structure src └── store ├── index.js # Vuex init, imports modules, main store └── modules ├── todo.js # 'todo' namespace └── todo-types.js 18
  • 19. Vuex store definition // src/store/index.js export const defaultState = { /* ... */ } export const getters = { /* ... */ } export const mutations = { /* ... */ } export const actions = { /* ... */ } export const modules = { /* ... */ } export default new Vuex.Store({ state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x // or state: { ...defaultState }, // Vue 2.x + Vuex 3.x getters, mutations, actions, modules, }) 19
  • 20. Vuex store module // src/store/modules/todo.js export const defaultState = { /* ... */ } export const getters = { /* ... */ } export const mutations = { /* ... */ } export const actions = { /* ... */ } export default { state: () => ({ ...defaultState }), // Vue 3.x + Vuex 4.x // or state: { ...defaultState }, // Vue 2.x + Vuex 3.x getters, mutations, actions, namespaced: true, } 20
  • 21. MIGRATE STORE DEFINITION --- a/src/store/index.js +++ b/src/stores/main.js -import { createStore } from 'vuex' +import { defineStore } from 'pinia' -export default createStore({ +export const useStore = defineStore('main', { - state: () => ({ ...defaultState }), + state: () => defaultState, getters, - mutations, actions, - modules, }) 21
  • 22. MIGRATE STORE GETTERS State? No changes! --- a/src/store/index.js +++ b/src/stores/main.js export const getters = { isEven: state => state.count % 2 === 0, - isOdd(state, getters) { - return !getters.isEven + isOdd() { + return !this.isEven }, } 22
  • 23. MIGRATE STORE MUTATIONS mutations → actions --- a/src/store/index.js +++ b/src/stores/main.js -export const mutations = { +export const actions = { - increment(state, payload) { + increment(payload) { // or multiple args - state.count++ + this.count++ }, } 23
  • 24. MIGRATE STORE ACTIONS --- a/src/store/index.js +++ b/src/stores/main.js export const actions = { - incrementAsync({ commit, dispatch, state }, payload) { + incrementAsync(payload) { // or multiple args setTimeout(() => { + // use `this` instead of `commit, dispatch, state` - commit('increment') + this.increment() }, 1000) }, } 24
  • 28. TEST MIGRATION state: no changes getters use .call on a getter when using this to access other getters -expect(getters.isOdd({}, { isEven: false })).toBe(true) +expect(getters.isOdd.call({ isEven: false })).toBe(true) 28
  • 29. TEST MIGRATION mutations: → actions actions use .call to pass state/getters/actions -const context = { commit: vi.fn() } -actions.incrementAsync(context) +const state = { increment: vi.fn() } +actions.incrementAsync.call(state) // ... -expect(context.commit).toHaveBeenCalled() +expect(state.increment).toHaveBeenCalled() 29
  • 30. STATE TEST MIGRATION import { test, expect } from 'vitest' import { defaultState } from '@/stores/main' test('defaultState should be as expected', () => { expect(defaultState).toEqual({ count: 0, }) }) 30
  • 31. GETTERS TEST MIGRATION import { test, expect } from 'vitest' import { getters } from '@/stores/main' test('isEven should work as expected', () => { expect(getters.isEven({ count: 0 })).toBe(true) expect(getters.isEven({ count: 1 })).toBe(false) }) test('isOdd should work as expected', () => { - expect(getters.isOdd(undefined, { isEven: true })) + expect(getters.isOdd.call({ isEven: true })) .toBe(false) - expect(getters.isOdd(undefined, { isEven: false })) + expect(getters.isOdd.call({ isEven: false })) .toBe(true) }) 31
  • 32. MUTATIONS TEST MIGRATION import { test, expect } from 'vitest' -import { mutations } from '@/store/index' +import { actions } from '@/stores/main' test('increment should work as expected', () => { const state = { count: 0 } - mutations.increment(state) + actions.increment.call(state) expect(state.count).toBe(1) - mutations.increment(state) + actions.increment.call(state) expect(state.count).toBe(2) }) 32
  • 33. ACTIONS TEST MIGRATION import { test, expect, vi } from 'vitest' -import { actions } from '@/store/index' +import { actions } from '@/stores/main' test('incrementAsync should work as expected', () => { vi.useFakeTimers() - const context = { commit: vi.fn() } - actions.incrementAsync(context) + const state = { increment: vi.fn() } + actions.incrementAsync.call(state) - expect(context.commit).not.toHaveBeenCalled() + expect(state.increment).not.toHaveBeenCalled(); vi.advanceTimersByTime(1000) - expect(context.commit).toHaveBeenCalledWith('increment') + expect(state.increment).toHaveBeenCalled(); vi.useRealTimers() }) 33
  • 34. COMPONENT MIGRATION (VUE 3) import { computed } from 'vue' -import { useStore } from 'vuex' +import { useStore } from '@/stores/main' const store = useStore() -const count = computed(() => store.state.count) -const isEven = computed(() => store.getters.isEven) -const isOdd = computed(() => store.getters.isOdd) +const count = computed(() => store.count) +const isEven = computed(() => store.isEven) +const isOdd = computed(() => store.isOdd) -const increment = () => store.commit('increment') -const incrementAsync = () => store.dispatch('incrementAsync') +const { increment, incrementAsync } = store 34
  • 35. COMPONENT MIGRATION (VUE 3) -import { computed } from 'vue' +import { storeToRefs } from 'pinia' -import { useStore } from 'vuex' +import { useStore } from '@/stores/main' const store = useStore() -const count = computed(() => store.state.count) -const isEven = computed(() => store.getters.isEven) -const isOdd = computed(() => store.getters.isOdd) +const { count, isEven, isOdd } = storeToRefs(store) -const increment = () => store.commit('increment') -const incrementAsync = () => store.dispatch('incrementAsync') +const { increment, incrementAsync } = store 35
  • 36. COMPONENT MIGRATION (VUE 2) -import { mapState, mapGetters, mapMutations, mapActions } fro +import { mapState, mapActions } from 'pinia' +import { useStore } from '@/stores/main' computed: { - ...mapState(['count']), - ...mapGetters(['isEven', 'isOdd']), + ...mapState(useStore, ['count', 'isEven', 'isOdd']), }, methods: { - ...mapMutations(['increment']), - ...mapActions(['incrementAsync']), + ...mapActions(useStore, ['increment', 'incrementAsync']), }, 36
  • 37. COMPONENT MIGRATION (VUE 2) -import { createNamespacedHelpers } from 'vuex' -import { GET_TODO_LIST } from '@/store/modules/todo-types' -const { mapState, mapActions } = createNamespacedHelpers('tod +import { mapState, mapActions } from 'pinia' +import { useTodoStore } from '@/stores/todo' computed: { - ...mapState(['todoList']), + ...mapState(useTodoStore, ['todoList']), }, mounted() { - this[GET_TODO_LIST]() + this.getTodoList() }, methods: { - ...mapActions([GET_TODO_LIST]), + ...mapActions(useTodoStore, ['getTodoList']), }, 37
  • 38. COMPONENT TESTING @pinia/testing npm install -D @pinia/testing import { createTestingPinia } from '@pinia/testing' createTestingPinia({ createSpy: vi.fn, initialState: { main: { count: 0 } }, }) 38
  • 39. createTestingPinia automatically mocks all actions unit test store and components separately allows you to overwrite getter values in tests (not working with Vue 2 and Jest) 39
  • 40. COMPONENT TESTING (VUE 3 + VITEST) import { createTestingPinia } from '@pinia/testing' import { shallowMount } from '@vue/test-utils'; import { useStore } from '@/stores/main' let pinia, store beforeEach(() => { pinia = createTestingPinia({ createSpy: vi.fn, initialState: { main: { count: 0 } }, }) // init store only after creating a testing pinia store = useStore() }) // tests const wrapper = shallowMount(Component, { global: { plugins: [pinia] } }) 40
  • 41. COMPONENT TESTING (VUE 2 + JEST) import { PiniaVuePlugin } from 'pinia' import { createTestingPinia } from '@pinia/testing' import { shallowMount, createLocalVue } from '@vue/test-utils' import { useStore } from '@/stores/main' const localVue = createLocalVue() localVue.use(PiniaVuePlugin) let pinia, store beforeEach(() => { pinia = createTestingPinia({ initialState: { main: { count: 0 } }, }) // init store only after creating a testing pinia store = useStore() }) // tests const wrapper = shallowMount(Component, { localVue, pinia }) 41
  • 42. COMPONENT TESTING (VUE 2 + JEST) Getters are not writable, you need to set the correct state in order to make them work as expected. store.count = 1 // or store.$patch({ count: 1, // other properties }) 42
  • 43. COMPONENT TEST MIGRATION (VUE 3 + VITEST) +import { createTestingPinia } from '@pinia/testing' import { describe, test, beforeEach, expect, vi } from 'vites -import { createStore } from 'vuex' import { shallowMount } from '@vue/test-utils'; import Counter from '@/components/Counter.vue' +import { useStore } from '@/stores/main' 43
  • 44. COMPONENT TEST MIGRATION (VUE 3 + VITEST) -let store +let pinia, store -let mutations = { increment: vi.fn() } -let actions = { incrementAsync: vi.fn() } beforeEach(() => { - store = createStore({ - state: () => ({ count: 0 }), - getters: { isEven: () => true, isOdd: () => false }, - mutations, - actions, - }) + pinia = createTestingPinia({ + createSpy: vi.fn, + initialState: { main: { count: 0 } }, + }) + store = useStore() }) 44
  • 45. COMPONENT TEST MIGRATION (VUE 3 + VITEST) test('should respect the snapshot', () => { const wrapper = shallowMount(Counter, { - global: { plugins: [store] } + global: { plugins: [pinia] } }) expect(wrapper.element).toMatchSnapshot() wrapper.findAll('button')[0].trigger('click') - expect(mutations.increment).toHaveBeenCalled() + expect(store.increment).toHaveBeenCalled() wrapper.findAll('button')[1].trigger('click') - expect(actions.incrementAsync).toHaveBeenCalled() + expect(store.incrementAsync).toHaveBeenCalled() }) 45
  • 46. COMPONENT TEST MIGRATION (VUE 2 + JEST) -import Vuex from 'vuex' +import { PiniaVuePlugin } from 'pinia' +import { createTestingPinia } from '@pinia/testing' import { shallowMount, createLocalVue } from '@vue/test-utils import Counter from '@/components/Counter.vue' +import { useStore } from '@/stores/main' const localVue = createLocalVue() -localVue.use(Vuex) +localVue.use(PiniaVuePlugin) 46
  • 47. COMPONENT TEST MIGRATION (VUE 2 + JEST) -let store +let pinia, store -let mutations = { increment: jest.fn() } -let actions = { incrementAsync: jest.fn() } beforeEach(() => { - store = createStore({ - state: { count: 0 }, - getters: { isEven: () => true, isOdd: () => false }, - mutations, - actions, - }) + pinia = createTestingPinia({ + initialState: { main: { count: 0 } }, + }) + store = useStore() }) 47
  • 48. COMPONENT TEST MIGRATION (VUE 2 + JEST) test('should respect the snapshot', () => { const wrapper = shallowMount(Counter, { - localVue, store, + localVue, pinia, }) expect(wrapper.element).toMatchSnapshot() wrapper.findAll('button')[0].trigger('click') - expect(mutations.increment).toHaveBeenCalled() + expect(store.increment).toHaveBeenCalled() wrapper.findAll('button')[1].trigger('click') - expect(actions.incrementAsync).toHaveBeenCalled() + expect(store.incrementAsync).toHaveBeenCalled() }) 48
  • 49. EVRYTHING SHOULD BE FINE, RIGHT? 49
  • 50. MIGRATION PROBLEMS NOTES Direct $store usage Use the correct store $store.state.propertyName $store.state.moduleName.propertyName $store.getters.getterName $store.getters['moduleName/getterName'] const myStore = useMyStore() myStore.propertyName myStore.getterName 50
  • 51. MIGRATION PROBLEMS NOTES Direct $store commit/dispatch Use store actions $store.commit.mutationName() $store.commit.moduleName.mutationName() $store.dispatch.actionName() $store.dispatch['moduleName/actionName']() const myStore = useMyStore() myStore.actionName() 51
  • 52. MIGRATION PROBLEMS NOTES What about a global useStore()? If used outside of <script setup>, will give you this error import { useStore } from '@/stores/main' const store = useStore() // other stuff getActivePinia was called with no active Pinia. Did you forget to install pinia? 52
  • 53. MIGRATION PROBLEMS NOTES Can Vuex and Pinia coexist? Yes, but... make sure to migrate entire modules, not entire components. 53
  • 54. MIGRATION PROBLEMS NOTES How to test a real store? import { setActivePinia, createPinia } from 'pinia' beforeEach(() => { setActivePinia(createPinia()) }) test('should work as expected', () => { const store = useStore() // ... }) 54
  • 55. MIGRATION PROBLEMS NOTES What about store persistence? myStore.$subscribe((mutation, state) => { localStorage.setItem('myStore', JSON.stringify(state)) }) // restore with myStore.$state = { /* ... */ } watch( pinia.state, (state) => { localStorage.setItem('piniaState', JSON.stringify(state)) }, { deep: true } ) // restore with pinia.state.value = { /* ... */ } https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state 55
  • 56. FINAL TASKS 1. remove Vuex store from main.js -import store from './store' // Vue 3 -app.use(store) // Vue 2 new Vue({ router, - store, pinia, render: h => h(App), }).$mount('#app') 56
  • 57. FINAL TASKS 2. delete Vuex store and tests 3. uninstall Vuex dependencies rm -rf src/store rm -rf tests/unit/store npm uninstall vuex @vue/cli-plugin-vuex 57