You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.1 KiB
7.1 KiB
Vue 2 to Vue 3 Migration Guide
Quick Start
Current State
- Application: Vue 3 / Quasar 2
- Infrastructure: Vue 3 compatible
- Components: Still using Options API (Vue 2 patterns)
- Goal: Gradual migration without breaking changes
What Changed?
18 files updated for Vue 2/3 compatibility - no breaking changes to existing code.
Files Changed Summary
| Category | Files | Status | Action Required |
|---|---|---|---|
| Helpers | store-helper.js |
Compatible | None - keep using |
| Boot Files | event-bus.js, filters.js, constants.js, appConfig.js, vuelidate.js |
Enhanced | None - new features available |
| API Layer | api/common.js |
Fixed | None - transparent fix |
| Store | store/common.js, store/apiHelper.js |
New helpers | Optional - use in new code |
| Composables | useStore.js, useGlobals.js |
Store access helpers | Use when converting to Composition API |
Key Changes by Use Case
Use Case 1: Working with Existing Components
Answer: Nothing changes!
// This still works exactly the same
export default {
computed: {
...mapState('user', ['subscriber']),
...mapGetters('user', ['isLogged'])
},
methods: {
...mapActions('user', ['login']),
formatDate() {
return this.$filters.readableDate(new Date())
}
}
}
Use Case 2: Converting to Composition API
Answer: Use composables
// Old Options API
export default {
computed: {
...mapGetters('user', ['isLogged', 'username'])
}
}
// New Composition API
import { useGetters } from 'src/composables/useStore'
export default {
setup() {
const { isLogged, username } = useGetters('user', ['isLogged', 'getUsername'])
return { isLogged, username }
}
}
Use Case 3: Using in Services/Utilities
Answer: Direct imports
// NEW: Can now import and use anywhere
import { $emit } from 'src/boot/event-bus'
import { readableDate } from 'src/boot/filters'
import { appConfig } from 'src/boot/appConfig'
import { store } from 'src/boot/store'
export function myService() {
$emit('event', data)
const formatted = readableDate(new Date())
const url = appConfig.baseHttpUrl
const user = store.state.user.subscriber
}
Available Composables
When converting components to Composition API:
| Composable | Purpose | Use When |
|---|---|---|
useGetters(module, keys) |
Access store getters | Need reactive computed state |
useActions(module, keys) |
Access store actions | Need to dispatch actions |
useState(module, keys) |
Access store state | Need raw state values |
useFilters() |
Formatting functions | Dates, numbers, currencies |
useAppConfig() |
App configuration | Need API URLs, settings |
useValidationErrors() |
Form validation | Vuelidate error messages |
See doc/composables.md for detailed usage examples.
Common Migration Patterns
Pattern 1: Simple State Access
// Before (Options API)
computed: {
...mapState('user', ['subscriber'])
}
// After (Composition API)
const { subscriber } = useState('user', ['subscriber'])
Pattern 2: Actions with Loading State
// Before
computed: {
...mapGetters('user', ['loginRequesting'])
},
methods: {
...mapActions('user', ['login'])
}
// After
const { loginRequesting } = useGetters('user', ['loginRequesting'])
const { login } = useActions('user', ['login'])
Pattern 3: Filters
// Before
this.$filters.readableDate(new Date())
// After - Option 1: Direct import
import { readableDate } from 'src/boot/filters'
const formatted = readableDate(new Date())
// After - Option 2: Composable
const filters = useFilters()
const formatted = filters.readableDate(new Date())
Future-Proofing: Pinia Migration
The composable helper pattern is designed to make eventual Pinia migration as easy as possible.
Why Avoid useMutations
Pinia has no mutations! In Pinia, actions can directly modify state:
// ❌ AVOID: useMutations (won't work with Pinia)
import { useMutations } from 'src/composables/useStore'
const { setProfile } = useMutations('user', ['setProfile'])
// ✅ USE: useActions instead (works with both Vuex & Pinia)
import { useActions } from 'src/composables/useStore'
const { setProfile } = useActions('user', ['setProfile'])
Vuex Store Pattern (Transition-Ready)
Wrap mutations in actions, even for simple setters:
// Vuex store (current)
export default {
state: { profile: null },
mutations: {
setProfile(state, data) {
state.profile = data
}
},
actions: {
setProfile({ commit }, data) {
commit('setProfile', data) // Action wraps mutation
}
}
}
When migrating to Pinia later, only the store changes and components stay identical:
// Pinia store (future)
export const useUserStore = defineStore('user', {
state: () => ({ profile: null }),
actions: {
setProfile(data) {
this.profile = data // Direct assignment
}
}
})
Migration Checklist
When converting a component to Composition API:
- Replace
mapStatewith composable state access - Replace
mapGetterswith composable getters - Replace
mapActionswith composable actions - Avoid
useMutations- useuseActionsinstead (Pinia-ready pattern) - Replace
this.$filterswith direct imports oruseFilters() - Replace
this.$appConfigwithuseAppConfig()or direct import - Replace
this.emitter.$emitwitheventBus.$emitor direct import - Replace
this.$errMsgwithgetErrorMessageoruseValidationErrors() - Update refs from
this.$refstoref()pattern - Add proper lifecycle hooks (
onMounted,onUnmounted) - Test component works the same as before
Next Steps
- Read this guide
- Review composables documentation:
doc/composables.md - Try converting one simple component to get familiar with patterns
- Ask questions if stuck
For detailed API documentation, see:
doc/composables.md- Composable usage guide with examplesdoc/data-layer.md- Store patterns and helpers- Vue 3 docs: https://vuejs.org/guide/introduction.html
Internationalization (i18n)
Current Configuration
// src/boot/i18n.js
legacy: true // Supports both Options API and Composition API
Usage in Options API (Current)
export default {
computed: {
title() {
return this.$t('page.title')
}
},
methods: {
changeLanguage() {
this.$i18n.locale = 'de'
}
}
}
Usage in Composition API (New)
import { useI18n } from 'vue-i18n'
export default {
setup() {
const { t, locale } = useI18n()
const title = computed(() => t('page.title'))
const changeLanguage = () => {
locale.value = 'de'
}
return { title, changeLanguage }
}
}
Migration Notes
- Keep
legacy: trueuntil all components migrated to Composition API - Both patterns work simultaneously with
legacy: true - Change to
legacy: falseonly after complete migration (far future)