在开发Vue 3项目时,我们经常使用响应式系统来处理表单数据。但在某些情况下,不当的响应式使用方式在开发环境可能表现正常,而在生产环境构建后会出现严重的性能问题,如页面卡顿甚至完全卡死。本文将通过一个实际案例来分析这类问题及其解决方案。
在我们的商品信息页面中,有一个动态表单渲染组件FormTemplateRender
,用于根据后端返回的模板动态生成表单。在开发环境(npm run dev
)下一切正常,但在生产环境构建后(npm run build
),用户在动态输入框中输入文字时页面会完全卡死。
以下是导致问题的原始代码实现:
// 错误的响应式处理方式
const formData = ref<any[]>([...(props.modelValue || [])])
const formModal = computed(() => {
return props.modelValue.reduce((acc: Record<string, any>, item) => {
acc[item.flag] = item.value
return acc
}, {})
})
// 监听 props 变化并更新本地副本
watch(
() => props.modelValue,
(newValue) => {
formData.value = [...(newValue || [])]
},
)
// 监听本地数据变化并同步给父组件
watch(
() => formData.value,
(newValue) => {
emit('update:modelValue', [...newValue])
},
{ deep: true },
)
这段代码存在以下问题:
formData
和props.modelValue
之间存在相互触发更新的可能{ deep: true }
对复杂对象进行深度监听,在生产环境中构建优化后会引发性能瓶颈formModal
计算属性与formData
响应式数据同时使用,可能导致Vue响应式系统在生产环境中的优化处理出现问题
// 正确的响应式处理方式
const formData = ref<FormTemplate.Arg[]>([])
// 监听 props 变化并更新本地副本
watch(
() => props.modelValue,
(newValue) => {
// 创建新的数组,避免直接修改props
formData.value = (newValue || []).map(item => ({
...item,
value: item.value ?? '',
}))
// 更新表单模型
updateFormModal()
// 构建验证规则
buildFormRules()
},
{ immediate: true },
)
// 防止循环更新的标志
let isUpdating = false
watch(
() => formData.value,
(newData) => {
// 避免循环更新
if (isUpdating)
return
isUpdating = true
try {
// 发送更新事件给父组件
emit('update:modelValue', newData)
}
finally {
// 确保在下一个tick重置标志
setTimeout(() => {
isUpdating = false
}, 0)
}
},
{ deep: true },
)
通过引入isUpdating
标志变量,防止在数据更新时触发循环调用:
let isUpdating = false
watch(
() => formData.value,
(newData) => {
if (isUpdating)
return
isUpdating = true
try {
emit('update:modelValue', newData)
}
finally {
setTimeout(() => {
isUpdating = false
}, 0)
}
},
{ deep: true },
)
在监听到props变化时,正确地创建新数组而不是直接引用:
formData.value = (newValue || []).map(item => ({
...item,
value: item.value ?? '',
}))
将数据流分为两个方向:
避免了两个方向同时进行深度监听造成的性能问题。
在Vue 3开发中,尤其是在构建需要在生产环境中运行的应用时,需要注意响应式系统的正确使用方式:
通过以上优化,我们成功解决了表单组件在生产环境中输入卡死的问题,提升了用户体验。
正在学习Go语言的PHP程序员。