Da Vuex a Pinia: come fare la migrazione

Commit University
Commit UniversityChief Operating Officer at MaCoEv
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
1 of 59

Recommended

webpack 101 slides by
webpack 101 slideswebpack 101 slides
webpack 101 slidesmattysmith
695 views17 slides
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter... by
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Domenic Denicola
184.3K views99 slides
Build RESTful API Using Express JS by
Build RESTful API Using Express JSBuild RESTful API Using Express JS
Build RESTful API Using Express JSCakra Danu Sedayu
389 views41 slides
React and redux by
React and reduxReact and redux
React and reduxMystic Coders, LLC
2.9K views39 slides
Spring Security by
Spring SecuritySpring Security
Spring SecuritySumit Gole
1.1K views14 slides
Better web apps with React and Redux by
Better web apps with React and ReduxBetter web apps with React and Redux
Better web apps with React and ReduxAli Sa'o
203 views91 slides

More Related Content

What's hot

Is it time to migrate to Vue 3? by
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Denny Biasiolli
321 views74 slides
Introduction à React JS by
Introduction à React JSIntroduction à React JS
Introduction à React JSAbdoulaye Dieng
2.2K views51 slides
Redux training by
Redux trainingRedux training
Redux trainingdasersoft
239 views47 slides
The Log4Shell Vulnerability – explained: how to stay secure by
The Log4Shell Vulnerability – explained: how to stay secureThe Log4Shell Vulnerability – explained: how to stay secure
The Log4Shell Vulnerability – explained: how to stay secureKaspersky
1.2K views17 slides
React with Redux by
React with ReduxReact with Redux
React with ReduxStanimir Todorov
330 views32 slides
Redux Toolkit - Quick Intro - 2022 by
Redux Toolkit - Quick Intro - 2022Redux Toolkit - Quick Intro - 2022
Redux Toolkit - Quick Intro - 2022Fabio Biondi
530 views45 slides

What's hot(20)

Redux training by dasersoft
Redux trainingRedux training
Redux training
dasersoft239 views
The Log4Shell Vulnerability – explained: how to stay secure by Kaspersky
The Log4Shell Vulnerability – explained: how to stay secureThe Log4Shell Vulnerability – explained: how to stay secure
The Log4Shell Vulnerability – explained: how to stay secure
Kaspersky1.2K views
Redux Toolkit - Quick Intro - 2022 by Fabio Biondi
Redux Toolkit - Quick Intro - 2022Redux Toolkit - Quick Intro - 2022
Redux Toolkit - Quick Intro - 2022
Fabio Biondi530 views
Express JS by Alok Guha
Express JSExpress JS
Express JS
Alok Guha2.5K views
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016 by Frans Rosén
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
The Secret Life of a Bug Bounty Hunter – Frans Rosén @ Security Fest 2016
Frans Rosén9.2K views
20220716_만들면서 느껴보는 POP by Chiwon Song
20220716_만들면서 느껴보는 POP20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP
Chiwon Song373 views
Edge Side Include Injection: Abusing Caching Servers into SSRF and Transparen... by Priyanka Aash
Edge Side Include Injection: Abusing Caching Servers into SSRF and Transparen...Edge Side Include Injection: Abusing Caching Servers into SSRF and Transparen...
Edge Side Include Injection: Abusing Caching Servers into SSRF and Transparen...
Priyanka Aash500 views
N vision by sri44
N visionN vision
N vision
sri442.4K views
Node.js Express by Eyal Vardi
Node.js  ExpressNode.js  Express
Node.js Express
Eyal Vardi4.8K views
Chap 03 poo en java partie1 by Yassine Badri
Chap 03 poo en java partie1Chap 03 poo en java partie1
Chap 03 poo en java partie1
Yassine Badri1.2K views

Similar to Da Vuex a Pinia: come fare la migrazione

State manager in Vue.js, from zero to Vuex by
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 VuexCommit University
159 views65 slides
Introducing Vuex in your project by
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your projectDenny Biasiolli
139 views65 slides
Is it time to migrate to Vue 3? by
Is it time to migrate to Vue 3?Is it time to migrate to Vue 3?
Is it time to migrate to Vue 3?Denny Biasiolli
138 views75 slides
Side effects-con-redux by
Side effects-con-reduxSide effects-con-redux
Side effects-con-reduxNicolas Quiceno Benavides
164 views59 slides
How to Build SPA with Vue Router 2.0 by
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.0Takuya Tejima
18.5K views22 slides
How to build to do app using vue composition api and vuex 4 with typescript by
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 typescriptKaty Slemon
73 views29 slides

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

State manager in Vue.js, from zero to Vuex by Commit University
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
Commit University159 views
Introducing Vuex in your project by Denny Biasiolli
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your project
Denny Biasiolli139 views
How to Build SPA with Vue Router 2.0 by Takuya Tejima
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
Takuya Tejima18.5K views
How to build to do app using vue composition api and vuex 4 with typescript by Katy Slemon
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
Katy Slemon73 views
React + Redux. Best practices by Clickky
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
Clickky5.8K views
Pablo Magaz | ECMAScript 2018 y más allá | Codemotion Madrid 2018 by Codemotion
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
Codemotion140 views
Gutenberg sous le capot, modules réutilisables by Riad Benguella
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
Riad Benguella1.6K views
Building Universal Web Apps with React ForwardJS 2017 by Elyse Kolker Gordon
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 by Astrails
Building and deploying React applicationsBuilding and deploying React applications
Building and deploying React applications
Astrails2.1K views
Relay Modern: architecture and workflow by Alex Alexeev
Relay Modern: architecture and workflowRelay Modern: architecture and workflow
Relay Modern: architecture and workflow
Alex Alexeev652 views
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app by Katy Slemon
Beginner’s tutorial (part 2) how to integrate redux-saga in react native appBeginner’s tutorial (part 2) how to integrate redux-saga in react native app
Beginner’s tutorial (part 2) how to integrate redux-saga in react native app
Katy Slemon112 views

More from Commit University

Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ... by
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 ...Commit University
28 views53 slides
Vue.js slots.pdf by
Vue.js slots.pdfVue.js slots.pdf
Vue.js slots.pdfCommit University
26 views68 slides
Commit - Qwik il framework che ti stupirà.pptx by
Commit - Qwik il framework che ti stupirà.pptxCommit - Qwik il framework che ti stupirà.pptx
Commit - Qwik il framework che ti stupirà.pptxCommit University
17 views37 slides
Sviluppare da zero una Angular Web App per la PA by
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 PACommit University
46 views7 slides
Backstage l'Internal Developer Portal Open Source per una migliore Developer ... by
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 ...Commit University
129 views48 slides
Prisma the ORM that node was waiting for by
Prisma the ORM that node was waiting forPrisma the ORM that node was waiting for
Prisma the ORM that node was waiting forCommit University
109 views47 slides

More from Commit University(20)

Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ... by Commit University
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 ...
Commit - Qwik il framework che ti stupirà.pptx by Commit University
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 by Commit University
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 ... by Commit University
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 ...
Commit University129 views
Prisma the ORM that node was waiting for by Commit University
Prisma the ORM that node was waiting forPrisma the ORM that node was waiting for
Prisma the ORM that node was waiting for
Commit University109 views
Decision-making for Software Development Teams - Commit University by 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 by Commit University
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 ... by Commit University
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 by Commit University
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 by Commit University
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
Commit University431 views
Alpine.js: the outsider Javascript framework by Commit University
Alpine.js: the outsider Javascript frameworkAlpine.js: the outsider Javascript framework
Alpine.js: the outsider Javascript framework
Commit University232 views
Modificazione del comportamento: come la Gamification cattura l'attenzione by Commit University
Modificazione del comportamento: come la Gamification cattura l'attenzioneModificazione del comportamento: come la Gamification cattura l'attenzione
Modificazione del comportamento: come la Gamification cattura l'attenzione
Commit University273 views
API moderne e real-time per applicazioni innovative by Commit University
API moderne e real-time per applicazioni innovativeAPI moderne e real-time per applicazioni innovative
API moderne e real-time per applicazioni innovative
Commit University128 views
Rilasci senza paura (o panico) con Azure DevOps by Commit University
Rilasci senza paura (o panico) con Azure DevOpsRilasci senza paura (o panico) con Azure DevOps
Rilasci senza paura (o panico) con Azure DevOps
Commit University211 views

Recently uploaded

Initiating and Advancing Your Strategic GIS Governance Strategy by
Initiating and Advancing Your Strategic GIS Governance StrategyInitiating and Advancing Your Strategic GIS Governance Strategy
Initiating and Advancing Your Strategic GIS Governance StrategySafe Software
198 views68 slides
Measurecamp Brussels - Synthetic data.pdf by
Measurecamp Brussels - Synthetic data.pdfMeasurecamp Brussels - Synthetic data.pdf
Measurecamp Brussels - Synthetic data.pdfHuman37
27 views14 slides
KubeConNA23 Recap.pdf by
KubeConNA23 Recap.pdfKubeConNA23 Recap.pdf
KubeConNA23 Recap.pdfMichaelOLeary82
28 views27 slides
Business Analyst Series 2023 - Week 4 Session 8 by
Business Analyst Series 2023 -  Week 4 Session 8Business Analyst Series 2023 -  Week 4 Session 8
Business Analyst Series 2023 - Week 4 Session 8DianaGray10
180 views13 slides
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And... by
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...ShapeBlue
120 views12 slides
Deep Tech and the Amplified Organisation: Core Concepts by
Deep Tech and the Amplified Organisation: Core ConceptsDeep Tech and the Amplified Organisation: Core Concepts
Deep Tech and the Amplified Organisation: Core ConceptsHolonomics
17 views21 slides

Recently uploaded(20)

Initiating and Advancing Your Strategic GIS Governance Strategy by Safe Software
Initiating and Advancing Your Strategic GIS Governance StrategyInitiating and Advancing Your Strategic GIS Governance Strategy
Initiating and Advancing Your Strategic GIS Governance Strategy
Safe Software198 views
Measurecamp Brussels - Synthetic data.pdf by Human37
Measurecamp Brussels - Synthetic data.pdfMeasurecamp Brussels - Synthetic data.pdf
Measurecamp Brussels - Synthetic data.pdf
Human37 27 views
Business Analyst Series 2023 - Week 4 Session 8 by DianaGray10
Business Analyst Series 2023 -  Week 4 Session 8Business Analyst Series 2023 -  Week 4 Session 8
Business Analyst Series 2023 - Week 4 Session 8
DianaGray10180 views
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And... by ShapeBlue
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...
Enabling DPU Hardware Accelerators in XCP-ng Cloud Platform Environment - And...
ShapeBlue120 views
Deep Tech and the Amplified Organisation: Core Concepts by Holonomics
Deep Tech and the Amplified Organisation: Core ConceptsDeep Tech and the Amplified Organisation: Core Concepts
Deep Tech and the Amplified Organisation: Core Concepts
Holonomics17 views
Innovation & Entrepreneurship strategies in Dairy Industry by PervaizDar1
Innovation & Entrepreneurship strategies in Dairy IndustryInnovation & Entrepreneurship strategies in Dairy Industry
Innovation & Entrepreneurship strategies in Dairy Industry
PervaizDar139 views
The Power of Generative AI in Accelerating No Code Adoption.pdf by Saeed Al Dhaheri
The Power of Generative AI in Accelerating No Code Adoption.pdfThe Power of Generative AI in Accelerating No Code Adoption.pdf
The Power of Generative AI in Accelerating No Code Adoption.pdf
Saeed Al Dhaheri44 views
"Node.js Development in 2024: trends and tools", Nikita Galkin by Fwdays
"Node.js Development in 2024: trends and tools", Nikita Galkin "Node.js Development in 2024: trends and tools", Nikita Galkin
"Node.js Development in 2024: trends and tools", Nikita Galkin
Fwdays37 views
NTGapps NTG LowCode Platform by Mustafa Kuğu
NTGapps NTG LowCode Platform NTGapps NTG LowCode Platform
NTGapps NTG LowCode Platform
Mustafa Kuğu474 views
Business Analyst Series 2023 - Week 4 Session 7 by DianaGray10
Business Analyst Series 2023 -  Week 4 Session 7Business Analyst Series 2023 -  Week 4 Session 7
Business Analyst Series 2023 - Week 4 Session 7
DianaGray10152 views
Transcript: Redefining the book supply chain: A glimpse into the future - Tec... by BookNet Canada
Transcript: Redefining the book supply chain: A glimpse into the future - Tec...Transcript: Redefining the book supply chain: A glimpse into the future - Tec...
Transcript: Redefining the book supply chain: A glimpse into the future - Tec...
BookNet Canada43 views
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading... by The Digital Insurer
Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading...
"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell by Fwdays
"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell
"Node.js vs workers — A comparison of two JavaScript runtimes", James M Snell
Fwdays14 views
"Package management in monorepos", Zoltan Kochan by Fwdays
"Package management in monorepos", Zoltan Kochan"Package management in monorepos", Zoltan Kochan
"Package management in monorepos", Zoltan Kochan
Fwdays37 views
This talk was not generated with ChatGPT: how AI is changing science by Elena Simperl
This talk was not generated with ChatGPT: how AI is changing scienceThis talk was not generated with ChatGPT: how AI is changing science
This talk was not generated with ChatGPT: how AI is changing science
Elena Simperl34 views
Bronack Skills - Risk Management and SRE v1.0 12-3-2023.pdf by ThomasBronack
Bronack Skills - Risk Management and SRE v1.0 12-3-2023.pdfBronack Skills - Risk Management and SRE v1.0 12-3-2023.pdf
Bronack Skills - Risk Management and SRE v1.0 12-3-2023.pdf
ThomasBronack31 views

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