一键导入
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)
}