Element Plus ElInput 的 formatter 坑
最近在用 Element Plus 的 <ElInput> 组件做一个限制最大值 + 格式化显示的需求时,踩了一个特别隐蔽的坑:
输入框显示被限制成了 500,但实际绑定的数据还是 1000。
看似小问题,其实背后涉及到 formatter / parser 的本质区别、Element Plus 的内部设计逻辑,以及 Vue 双向绑定的行为模型。
1. 需求背景
我有一个百分比输入框:
- 最大值不能超过 500(例如业务里代表最大 500%)
- 需要用 formatter 美化显示
- 双向绑定到表格里的某一列数据
代码如下:
<ElInput
type="number"
v-model={row[TableDatePresetsEnum[dateLabel]]}
placeholder="请输入"
formatter={moneyFormatter}
v-slots={{
append: () => '%',
}}
/>
问题出现了:
输入 1000,组件显示是 500(moneyFormatter 生效),但绑定的数据却是 1000!
2. 第一个误区:formatter ≠ 数据限制器
很多人直觉会认为:
formatter 会改变 v-model 的值
但实际上,Element Plus 官方文档也明确说了:
formatter 用于输入框显示格式化,只作用于 UI 层,不会影响真实的输入值。
换句话说:
-
formatter = 美化输出
-
parser = 解析输入
2.1 为什么 formatter 不改数据?
这是组件设计的哲学问题。formatter 的职责是单向输出格式化,它要做到的是:
-
保留用户的原始输入(可能是还没完成的输入)
-
在视觉上提供美化(千分位、货币符号、掩码等)
如果 formatter 同时去改数据,就会出现以下问题:
-
用户输入 "12."(还没输入小数位),formatter 改成 "12" → 小数点被吞掉
-
用户输入 "0005",formatter 改成 "5" → 原始意图丢失
这种情况在用户体验上是灾难性的,所以 Element Plus 选择让 formatter 只改显示,不动真实值。
3. 真实数据的修改要靠 parser
Element Plus 给了另一个属性 parser,用来做输入解析:
<ElInput
v-model={value}
formatter={(val) => `${val}%`} // 显示加个百分号
parser={(val) => {
const num = Number(val.replace('%', ''));
return num > 500 ? 500 : num; // 限制最大值
}}
/>
这样:输入 1000,parser 改为 500 写回 v-model,formatter 将 500 显示为 500%。
这才是官方推荐的方式。
或者使用 onInput 事件手动更改:
<ElInput
type="number"
modelValue={value}
onInput={(val) => {
const num = Number(val);
value = num > 500 ? 500 : num;
}}
formatter={(val) => `${val}%`} // 显示加个百分号
v-slots={{
append: () => '%',
}}
/>
4. Element Plus 源码
在 packages/components/input/src/input.vue 里,formatter 的调用是在渲染阶段:
const displayValue = computed(() => {
let formatted = inputValue.value
if (props.formatter) {
formatted = props.formatter(formatted)
}
return formatted
})
而 v-model 的值是通过事件 onInput 直接回传的,没有经过 formatter 的处理:
const handleInput = (event: Event) => {
const { value } = event.target as HTMLInputElement
emit('update:modelValue', value)
}
这就解释了为什么 UI 和数据会“脱节”。
4.1 延伸:其他框架的类似设计
这个 formatter / parser 分离的思想,不止 Element Plus 用:
-
Ant Design (InputNumber) 同样区分 formatter / parser
-
React Final Form 也是 format / parse 分离
这是表单组件的通用模式:
-
parse:将用户输入的字符串解析成数据模型
-
format:将数据模型转换成显示字符串
总结:这个问题表面上只是一个“为什么显示和数据不一致”的 Bug,但背后涉及:
-
Vue v-model 的数据流
-
UI 层与数据层解耦的设计哲学
-
表单组件 formatter / parser 的职责分离
-
用户体验与数据安全性的平衡
当理解了这些就会明白:
任何一个 UI 层的 API,只要它不承诺会改数据,那它就不应该被用来改数据。
更多推荐




所有评论(0)