Vue2自定义指令模拟计算属性getter/setter,解决无限循环问题
解决Vue2自定义指令实现输入框格式化的无限循环问题
问题背景
需求是实现输入框自动格式化:用户输入内容时自动添加符号(比如货币符$),获取真实业务值时移除符号。已知用计算属性的getter/setter可以实现,但尝试用Vue2自定义指令实现时触发了无限循环,相关代码如下:
计算属性实现示例
<template> <input type="text" v-model="computedTest" /><!-- 显示 20$ --> </template> <script> export default { data() { return { test: 20, } }, computed: { computedTest: { get() { return this.test + "$" }, set(val) { // 修正原代码正则错误:/$/g匹配行尾,需改为/\$/g匹配$符号 this.test = val.replace(/\$/g, "") }, }, methods: { doSomething() { console.log(this.test) // 输出真实值 20 }, }, }, } </script>
自定义指令尝试代码(存在无限循环)
组件代码:
<template> <input type="text" v-model="test" v-custom /> </template> <script> export default { data() { return { test: 0 } }, } </script>
v-custom指令代码:
export default { bind: function(el, binding, vnode) { Vue.set(el, "value", toLanguage(el.value, "en")) // toLanguage为自定义格式化函数 }, componentUpdated: function(el, binding, vnode) { // getContext自定义函数:返回组件上下文对象contextObj和v-model绑定字段model(示例中为'test') let { contextObj, model } = getContext(vnode) const vnodeValue = get(contextObj, model) // lodash get方法获取当前model值 // 解析输入值,移除符号 const parsedValue = parseInputValue(el, vnodeValue) ?? vnodeValue if (contextObj) { Vue.set(contextObj, model, parsedValue) } el.value = toLanguage(el.value, "en") el.dispatchEvent(new Event("input", { bubbles: true })) }, }
无限循环原因分析
- 组件更新循环触发:
componentUpdated钩子在组件更新后执行,内部修改contextObj[model]的值会再次触发组件更新,导致钩子反复执行,形成循环。 - 手动input事件加剧循环:手动调用
dispatchEvent("input")会再次触发v-model的更新逻辑,进一步强化循环链条。
解决方案
核心思路是区分用户输入触发和组件更新触发,仅在用户输入时执行格式化和值更新逻辑,同时添加值判断避免无意义的重复操作:
修正后的自定义指令代码
export default { bind: function(el, binding, vnode) { const { contextObj, model } = getContext(vnode) const rawValue = get(contextObj, model) // 初始化格式化显示值 el.value = toLanguage(rawValue, "en") // 监听用户输入事件,仅在用户输入时解析值 el.addEventListener('input', function(e) { const inputValue = e.target.value // 解析输入值得到真实业务值 const parsedValue = parseInputValue(el, inputValue) ?? inputValue if (contextObj) { // 仅当解析值与当前model值不同时才更新,避免不必要的组件更新 if (parsedValue !== get(contextObj, model)) { Vue.set(contextObj, model, parsedValue) } } }) // 监听业务值变化,同步更新输入框格式化显示 const watcher = new Vue({ data: { value: rawValue }, watch: { value(newVal) { const formattedVal = toLanguage(newVal, "en") // 仅当格式化后的值与输入框当前值不同时才更新,切断循环 if (el.value !== formattedVal) { el.value = formattedVal } } } }) // 保存watcher到元素上,用于销毁 el._formatWatcher = watcher }, unbind: function(el) { // 解绑事件并销毁watcher,避免内存泄漏 el.removeEventListener('input', null) if (el._formatWatcher) { el._formatWatcher.$destroy() delete el._formatWatcher } } }
关键改动说明
- 替换componentUpdated逻辑:改用
input事件监听用户输入,仅在用户主动输入时处理值解析和model更新。 - 添加业务值监听:通过独立watcher监听model值变化,同步更新输入框的格式化显示,且添加值判断避免重复更新。
- 增加值校验逻辑:无论是更新model还是输入框值,都先判断新值与当前值是否一致,不一致才执行更新,从根源切断循环触发条件。
- 完善销毁逻辑:在
unbind钩子中移除事件监听并销毁watcher,避免内存泄漏。
验证效果
使用修正后的指令后:
- 用户输入内容时,输入框自动显示格式化后的带符号内容,同时model保存移除符号的真实业务值。
- 代码修改model值时,输入框会自动同步为格式化后的显示值。
- 彻底解决无限循环问题。
内容的提问来源于stack exchange,提问作者Babaktrad
相关产品推荐
相关产品推荐

