بنقرة واحدة
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 المهني
| 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)
}
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