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.
290 lines
7.1 KiB
290 lines
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!**
|
|
|
|
```javascript
|
|
// 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**
|
|
|
|
```javascript
|
|
// 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**
|
|
|
|
```javascript
|
|
// 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
|
|
```javascript
|
|
// Before (Options API)
|
|
computed: {
|
|
...mapState('user', ['subscriber'])
|
|
}
|
|
|
|
// After (Composition API)
|
|
const { subscriber } = useState('user', ['subscriber'])
|
|
```
|
|
|
|
### Pattern 2: Actions with Loading State
|
|
```javascript
|
|
// Before
|
|
computed: {
|
|
...mapGetters('user', ['loginRequesting'])
|
|
},
|
|
methods: {
|
|
...mapActions('user', ['login'])
|
|
}
|
|
|
|
// After
|
|
const { loginRequesting } = useGetters('user', ['loginRequesting'])
|
|
const { login } = useActions('user', ['login'])
|
|
```
|
|
|
|
### Pattern 3: Filters
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// ❌ 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:
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
// 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 `mapState` with composable state access
|
|
- [ ] Replace `mapGetters` with composable getters
|
|
- [ ] Replace `mapActions` with composable actions
|
|
- [ ] **Avoid `useMutations`** - use `useActions` instead (Pinia-ready pattern)
|
|
- [ ] Replace `this.$filters` with direct imports or `useFilters()`
|
|
- [ ] Replace `this.$appConfig` with `useAppConfig()` or direct import
|
|
- [ ] Replace `this.emitter.$emit` with `eventBus.$emit` or direct import
|
|
- [ ] Replace `this.$errMsg` with `getErrorMessage` or `useValidationErrors()`
|
|
- [ ] Update refs from `this.$refs` to `ref()` pattern
|
|
- [ ] Add proper lifecycle hooks (`onMounted`, `onUnmounted`)
|
|
- [ ] Test component works the same as before
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Read this guide**
|
|
2. **Review composables documentation**: `doc/composables.md`
|
|
3. **Try converting one simple component** to get familiar with patterns
|
|
4. **Ask questions** if stuck
|
|
|
|
For detailed API documentation, see:
|
|
|
|
- `doc/composables.md` - Composable usage guide with examples
|
|
- `doc/data-layer.md` - Store patterns and helpers
|
|
- Vue 3 docs: <https://vuejs.org/guide/introduction.html>
|
|
|
|
## Internationalization (i18n)
|
|
|
|
### Current Configuration
|
|
|
|
```javascript
|
|
// src/boot/i18n.js
|
|
legacy: true // Supports both Options API and Composition API
|
|
```
|
|
|
|
### Usage in Options API (Current)
|
|
|
|
```javascript
|
|
export default {
|
|
computed: {
|
|
title() {
|
|
return this.$t('page.title')
|
|
}
|
|
},
|
|
methods: {
|
|
changeLanguage() {
|
|
this.$i18n.locale = 'de'
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Usage in Composition API (New)
|
|
|
|
```javascript
|
|
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: true` until all components migrated to Composition API
|
|
- Both patterns work simultaneously with `legacy: true`
|
|
- Change to `legacy: false` only after complete migration (far future)
|