원클릭으로
vue2-legacy
Vue 2.x Options API patterns with Vuex, mixins, filters, and reactivity caveats
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Vue 2.x Options API patterns with Vuex, mixins, filters, and reactivity caveats
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
RESTful API design patterns, HTTP standards, versioning, error handling, and documentation
Joomla 4/5 component development with MVC, services, plugins, and language handling
Joomla 3.x legacy patterns with JFactory, non-namespaced MVC, JDatabase, and plugin structure
Laravel best practices for Eloquent, controllers, validation, services, and testing
Performance optimization for PHP, JavaScript, databases, and caching strategies
PHP coding standards including strict typing, OOP patterns, error handling, and documentation
| name | vue2-legacy |
| description | Vue 2.x Options API patterns with Vuex, mixins, filters, and reactivity caveats |
| license | MIT |
| compatibility | opencode |
| metadata | {"framework":"vue","version":"2.5-2.7"} |
<template>
<div class="user-profile">
<div v-if="isLoading">Loading...</div>
<div v-else-if="errorMessage" class="error">{{ errorMessage }}</div>
<template v-else>
<img v-if="showAvatar" :src="user.avatar" :alt="fullName">
<h2>{{ fullName }}</h2>
<p>{{ user.created_at | formatDate }}</p>
</template>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
import UserMixin from '@/mixins/UserMixin'
export default {
name: 'UserProfile',
mixins: [UserMixin],
props: {
userId: {
type: Number,
required: true
},
showAvatar: {
type: Boolean,
default: true
}
},
data() {
return {
isLoading: false,
errorMessage: null
}
},
computed: {
...mapState('user', ['currentUser']),
...mapGetters('user', ['isAuthenticated']),
fullName() {
if (!this.user) return ''
return `${this.user.firstName} ${this.user.lastName}`
}
},
watch: {
userId: {
immediate: true,
handler(newId) {
this.fetchUser(newId)
}
}
},
created() {
// Called before mount, no DOM access
},
mounted() {
// DOM is available
},
beforeDestroy() {
// Cleanup (Vue 2 naming)
},
methods: {
...mapActions('user', ['fetchUserById']),
async fetchUser(id) {
this.isLoading = true
this.errorMessage = null
try {
await this.fetchUserById(id)
} catch (err) {
this.errorMessage = 'Failed to load user'
} finally {
this.isLoading = false
}
}
}
}
</script>
// BAD - Not reactive!
this.user.newProperty = 'value'
// GOOD - Use Vue.set
this.$set(this.user, 'newProperty', 'value')
// Or replace entire object
this.user = { ...this.user, newProperty: 'value' }
// BAD - These won't trigger updates!
this.items[index] = newValue
this.items.length = 0
// GOOD - Use Vue.set for index
this.$set(this.items, index, newValue)
// GOOD - Use splice for length
this.items.splice(0, this.items.length)
// GOOD - Mutation methods ARE reactive
this.items.push(item)
this.items.splice(index, 1)
this.items.sort()
data() {
return {
// Define ALL reactive properties upfront
user: null, // Will be populated async
items: [], // Empty array, not undefined
settings: { // Define nested structure
theme: 'light',
notifications: true
}
}
}
// store/modules/user.js
const state = {
currentUser: null,
users: [],
isLoading: false
}
const getters = {
isAuthenticated: state => state.currentUser !== null,
activeUsers: state => state.users.filter(u => u.isActive),
getUserById: state => id => state.users.find(u => u.id === id)
}
const mutations = {
SET_CURRENT_USER(state, user) {
state.currentUser = user
},
SET_USERS(state, users) {
state.users = users
},
SET_LOADING(state, isLoading) {
state.isLoading = isLoading
},
// For adding nested properties
UPDATE_USER_SETTINGS(state, { userId, settings }) {
const user = state.users.find(u => u.id === userId)
if (user) {
// Use Vue.set for new properties
Vue.set(user, 'settings', settings)
}
}
}
const actions = {
async fetchUsers({ commit }) {
commit('SET_LOADING', true)
try {
const response = await api.getUsers()
commit('SET_USERS', response.data)
} finally {
commit('SET_LOADING', false)
}
},
async fetchUserById({ commit }, id) {
const response = await api.getUser(id)
commit('SET_CURRENT_USER', response.data)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// Map state
...mapState('user', ['currentUser', 'isLoading']),
...mapState({
user: state => state.user.currentUser
}),
// Map getters
...mapGetters('user', ['isAuthenticated', 'activeUsers'])
},
methods: {
// Map mutations (rarely used directly)
...mapMutations('user', ['SET_CURRENT_USER']),
// Map actions
...mapActions('user', ['fetchUsers', 'fetchUserById'])
}
}
// mixins/FormMixin.js
export default {
data() {
return {
isSubmitting: false,
errors: {}
}
},
methods: {
clearErrors() {
this.errors = {}
},
setError(field, message) {
this.$set(this.errors, field, message)
},
async submitForm(action) {
if (this.isSubmitting) return
this.isSubmitting = true
this.clearErrors()
try {
await action()
} catch (err) {
if (err.response?.data?.errors) {
this.errors = err.response.data.errors
}
} finally {
this.isSubmitting = false
}
}
}
}
// Usage
import FormMixin from '@/mixins/FormMixin'
export default {
mixins: [FormMixin],
methods: {
async save() {
await this.submitForm(async () => {
await this.$store.dispatch('user/update', this.form)
})
}
}
}
Note: Mixins have drawbacks (naming collisions, unclear source). Consider extracting to utility functions where possible.
// Global filter (main.js)
Vue.filter('formatDate', function(value) {
if (!value) return ''
return new Date(value).toLocaleDateString()
})
Vue.filter('currency', function(value, symbol = '$') {
if (typeof value !== 'number') return value
return `${symbol}${value.toFixed(2)}`
})
// Local filter
export default {
filters: {
truncate(value, length = 50) {
if (!value || value.length <= length) return value
return value.substring(0, length) + '...'
}
}
}
<template>
<div>
<p>{{ createdAt | formatDate }}</p>
<p>{{ price | currency('€') }}</p>
<p>{{ description | truncate(100) }}</p>
</div>
</template>
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
// Emitting
EventBus.$emit('user-updated', userData)
// Listening (in component)
export default {
created() {
EventBus.$on('user-updated', this.handleUserUpdate)
},
beforeDestroy() {
// IMPORTANT: Always clean up!
EventBus.$off('user-updated', this.handleUserUpdate)
},
methods: {
handleUserUpdate(data) {
// Handle event
}
}
}
Warning: Event buses make data flow hard to track. Prefer Vuex or props/events.
<!-- Parent -->
<template>
<ChildComponent
:user="selectedUser"
@update="handleUpdate"
@delete="handleDelete"
/>
</template>
<!-- Child -->
<script>
export default {
props: {
user: {
type: Object,
required: true,
validator(value) {
return value.id && value.name
}
}
},
methods: {
save() {
// Use kebab-case for event names
this.$emit('update', this.user)
}
}
}
</script>
<!-- Parent -->
<Card>
<template v-slot:header>
<h2>Title</h2>
</template>
<template v-slot:default>
<p>Content</p>
</template>
<template v-slot:footer="{ canSubmit }">
<button :disabled="!canSubmit">Submit</button>
</template>
</Card>
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot></slot>
</div>
<div class="card-footer">
<slot name="footer" :canSubmit="isValid"></slot>
</div>
</div>
</template>
| Hook | Description |
|---|---|
beforeCreate | Before data/methods initialized |
created | Data reactive, no DOM |
beforeMount | Before render |
mounted | DOM available |
beforeUpdate | Before re-render |
updated | After re-render |
beforeDestroy | Before teardown (cleanup here!) |
destroyed | Component destroyed |
<template>
<!-- v-if vs v-show -->
<Modal v-if="isModalOpen" /> <!-- Destroys/creates DOM -->
<Tooltip v-show="isHovering" /> <!-- CSS display toggle -->
<!-- Always use :key -->
<UserCard v-for="user in users" :key="user.id" :user="user" />
<!-- Avoid method calls in templates for repeated renders -->
<!-- BAD -->
<div>{{ formatUser(user) }}</div>
<!-- GOOD - use computed -->
<div>{{ formattedUser }}</div>
</template>
<script>
export default {
computed: {
formattedUser() {
return `${this.user.firstName} ${this.user.lastName}`
}
}
}
</script>
// Route level
const UserDashboard = () => import('@/views/UserDashboard.vue')
// Component level
export default {
components: {
HeavyChart: () => import('@/components/HeavyChart.vue')
}
}
this in callbacks - Use arrow functions or bind// BAD
setTimeout(function() {
this.isLoading = false // 'this' is wrong!
}, 1000)
// GOOD
setTimeout(() => {
this.isLoading = false
}, 1000)
// BAD
this.user.name = 'New Name' // Mutating prop!
// GOOD
this.$emit('update', { ...this.user, name: 'New Name' })
beforeDestroy() {
EventBus.$off('event', this.handler)
window.removeEventListener('resize', this.onResize)
clearInterval(this.timer)
}