表单处理 #
表单处理的挑战 #
在 Vuex 中处理表单时,需要考虑:
- 严格模式下不能直接修改状态
- 表单需要双向绑定
- 表单验证和错误处理
- 表单重置和取消
基本方法 #
双向绑定计算属性 #
vue
<template>
<form @submit.prevent="submit">
<input v-model="userName" placeholder="Name" />
<input v-model="userEmail" placeholder="Email" />
<button type="submit">Submit</button>
</form>
</template>
<script>
export default {
computed: {
userName: {
get() {
return this.$store.state.user.name
},
set(value) {
this.$store.commit('SET_USER_NAME', value)
}
},
userEmail: {
get() {
return this.$store.state.user.email
},
set(value) {
this.$store.commit('SET_USER_EMAIL', value)
}
}
},
methods: {
submit() {
this.$store.dispatch('user/saveProfile')
}
}
}
</script>
使用 mapState 和 mapMutations #
vue
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState('user', ['name', 'email'])
},
methods: {
...mapMutations('user', ['SET_NAME', 'SET_EMAIL']),
updateName(e) {
this.SET_NAME(e.target.value)
},
updateEmail(e) {
this.SET_EMAIL(e.target.value)
}
}
}
</script>
局部副本模式 #
创建副本 #
vue
<template>
<form @submit.prevent="submit">
<input v-model="form.name" />
<input v-model="form.email" />
<button type="submit">Save</button>
<button type="button" @click="reset">Cancel</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {}
}
},
created() {
this.reset()
},
methods: {
reset() {
// 创建状态副本
this.form = { ...this.$store.state.user }
},
submit() {
this.$store.dispatch('user/updateProfile', this.form)
}
}
}
</script>
深拷贝 #
javascript
// 对于嵌套对象,使用深拷贝
created() {
this.form = JSON.parse(JSON.stringify(this.$store.state.user))
}
表单状态管理模块 #
创建表单模块 #
javascript
// store/modules/form.js
export default {
namespaced: true,
state: () => ({
values: {},
errors: {},
touched: {},
dirty: false,
submitting: false
}),
mutations: {
SET_VALUE(state, { field, value }) {
state.values[field] = value
state.dirty = true
},
SET_ERROR(state, { field, error }) {
state.errors[field] = error
},
CLEAR_ERROR(state, field) {
delete state.errors[field]
},
SET_TOUCHED(state, field) {
state.touched[field] = true
},
SET_SUBMITTING(state, submitting) {
state.submitting = submitting
},
RESET(state) {
state.values = {}
state.errors = {}
state.touched = {}
state.dirty = false
state.submitting = false
}
},
actions: {
async submit({ state, commit, dispatch }, { action, onSuccess }) {
commit('SET_SUBMITTING', true)
try {
await dispatch(action, state.values, { root: true })
onSuccess?.()
} catch (error) {
// 处理验证错误
if (error.errors) {
Object.entries(error.errors).forEach(([field, message]) => {
commit('SET_ERROR', { field, error: message })
})
}
throw error
} finally {
commit('SET_SUBMITTING', false)
}
}
},
getters: {
isValid: state => Object.keys(state.errors).length === 0,
hasChanges: state => state.dirty
}
}
使用表单模块 #
vue
<template>
<form @submit.prevent="submit">
<div>
<input
v-model="values.name"
@blur="touch('name')"
placeholder="Name"
/>
<span v-if="errors.name" class="error">{{ errors.name }}</span>
</div>
<div>
<input
v-model="values.email"
@blur="touch('email')"
placeholder="Email"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<button type="submit" :disabled="submitting || !isValid">
{{ submitting ? 'Saving...' : 'Save' }}
</button>
</form>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState('userForm', ['values', 'errors', 'submitting']),
...mapGetters('userForm', ['isValid', 'hasChanges'])
},
methods: {
...mapMutations('userForm', ['SET_VALUE', 'SET_TOUCHED', 'RESET']),
...mapActions('userForm', ['submit']),
touch(field) {
this.SET_TOUCHED(field)
},
async submit() {
await this.submit({
action: 'user/updateProfile',
onSuccess: () => {
this.$router.push('/profile')
}
})
}
},
created() {
// 初始化表单值
const user = this.$store.state.user
Object.entries(user).forEach(([key, value]) => {
this.SET_VALUE({ field: key, value })
})
}
}
</script>
表单验证 #
验证器函数 #
javascript
// utils/validators.js
export const validators = {
required: (value) => !!value || 'This field is required',
email: (value) => {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return pattern.test(value) || 'Invalid email format'
},
minLength: (min) => (value) =>
value.length >= min || `Minimum ${min} characters`,
maxLength: (max) => (value) =>
value.length <= max || `Maximum ${max} characters`,
match: (field) => (value, values) =>
value === values[field] || 'Values do not match'
}
验证 Action #
javascript
// store/modules/form.js
import { validators } from '@/utils/validators'
actions: {
validate({ state, commit }, rules) {
let isValid = true
Object.entries(rules).forEach(([field, fieldRules]) => {
const value = state.values[field]
for (const rule of fieldRules) {
const result = rule(value, state.values)
if (result !== true) {
commit('SET_ERROR', { field, error: result })
isValid = false
break
} else {
commit('CLEAR_ERROR', field)
}
}
})
return isValid
}
}
使用验证 #
vue
<script>
export default {
methods: {
async submit() {
const rules = {
name: [validators.required, validators.minLength(2)],
email: [validators.required, validators.email],
password: [validators.required, validators.minLength(8)],
confirmPassword: [
validators.required,
validators.match('password')
]
}
const isValid = await this.$store.dispatch('form/validate', rules)
if (isValid) {
await this.$store.dispatch('user/register', this.values)
}
}
}
}
</script>
动态表单 #
字段数组 #
javascript
// store/modules/form.js
state: () => ({
values: {
items: [{ name: '', quantity: 1 }]
}
}),
mutations: {
ADD_ITEM(state) {
state.values.items.push({ name: '', quantity: 1 })
},
REMOVE_ITEM(state, index) {
state.values.items.splice(index, 1)
},
UPDATE_ITEM(state, { index, field, value }) {
state.values.items[index][field] = value
}
}
动态表单组件 #
vue
<template>
<form @submit.prevent="submit">
<div v-for="(item, index) in items" :key="index">
<input v-model="item.name" placeholder="Item name" />
<input v-model.number="item.quantity" type="number" />
<button type="button" @click="removeItem(index)">Remove</button>
</div>
<button type="button" @click="addItem">Add Item</button>
<button type="submit">Submit</button>
</form>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState('orderForm', {
items: state => state.values.items
})
},
methods: {
...mapMutations('orderForm', ['ADD_ITEM', 'REMOVE_ITEM']),
addItem() {
this.ADD_ITEM()
},
removeItem(index) {
this.REMOVE_ITEM(index)
}
}
}
</script>
最佳实践 #
1. 使用局部副本处理复杂表单 #
javascript
// 复杂表单使用局部副本
data() {
return {
form: JSON.parse(JSON.stringify(this.$store.state.user))
}
}
2. 防抖输入 #
javascript
import { debounce } from 'lodash'
methods: {
updateValue: debounce(function(value) {
this.$store.commit('SET_VALUE', value)
}, 300)
}
3. 表单重置 #
javascript
methods: {
reset() {
this.form = { ...this.$store.state.user }
this.errors = {}
}
}
总结 #
表单处理要点:
| 方法 | 适用场景 |
|---|---|
| 计算属性 | 简单表单、少量字段 |
| 局部副本 | 复杂表单、需要取消功能 |
| 表单模块 | 大型表单、需要验证 |
| 动态表单 | 字段数量可变的表单 |
继续学习 热更新,了解开发时热更新配置。
最后更新:2026-03-28