news 2026/10/16 1:04:49

Vue过滤器从入门到实践:核心用法、链式调用与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue过滤器从入门到实践:核心用法、链式调用与避坑指南

1. 从一次页面渲染异常说起:为什么需要过滤器

先讲个真实场景。某次我在维护一个后台管理系统,订单列表页需要把后端返回的状态码展示成可读文案。后端给的是1、2、3、4,页面上要显示待支付、已支付、已发货、已完成。一开始我直接在模板里写了三四个三元表达式嵌套,代码大概长这样:

<span>{{ order.status === 1 ? '待支付' : order.status === 2 ? '已支付' : order.status === 3 ? '已发货' : '已完成' }}</span>

说实话,这种写法在首次上线时勉强能用,但后续需求一变更就崩了。产品经理说要把4改成已完成(可评价),同时新增一个5表示退款中。我翻遍模板找到那行三元表达式,改了五分钟才改对。后来仔细一琢磨,这种状态映射逻辑根本不应该写在模板里,应该用过滤器统一处理。

Vue 过滤器就是用来解决这类问题的。它的本质是一个可以在模板插值或v-bind表达式中使用的函数,接收原始数据,处理后再返回展示结果。比如上面的场景,用过滤器写就是:

<span>{{ order.status | statusText }}</span>
filters: { statusText(value) { const map = { 1: '待支付', 2: '已支付', 3: '已发货', 4: '已完成(可评价)', 5: '退款中' }; return map[value] || '未知状态'; } }

模板瞬间清爽了,状态和文案的映射关系也集中到了一个地方,后续改文案只动过滤器即可。

很多人觉得过滤器是个无关紧要的小功能,但实际项目里用好了,对模板可读性的提升是肉眼可见的。这篇文章我就把 Vue 过滤器的核心用法、注册方式、注意事项和踩坑经验一次性讲透,适合正在用 Vue 2 做项目、或者从 Vue 2 迁移 Vue 3 时被过滤器问题卡住的开发者参考。内容基于实际项目经验整理,不是官方文档的复读,重点放在“怎么用最合理”和“哪些坑必须避开”上。

2. Vue 过滤器的核心机制与适用场景

2.1 过滤器到底是什么

Vue 官方文档里的定义是:过滤器可以用在两个地方——双花括号插值和v-bind表达式。它由“管道符”|触发,前面的值会作为第一个参数传入过滤器函数。

<!-- 在双花括号插值中使用 --> <p>{{ message | capitalize }}</p> <!-- 在 v-bind 中使用 --> <div v-bind:id="rawId | formatId"></div>

需要看穿的一点是,过滤器的执行时机是组件渲染过程中,而不是数据变更时。也就是说,过滤器在每次重新渲染时都会重新执行,这一点影响性能评估。如果一个过滤器内部做了很重的计算(比如大量数据遍历、复杂正则匹配),每次都跑一遍,性能问题就会积累。这一点下面细讲。

过滤器的设计思路和 Unix 管道命令很像。数据像水流一样,从左到右流动,每一级管道做一次处理。多重过滤器也是这么链式调用的:

<p>{{ text | lowercase | capitalize }}</p>

text的值先传给lowercase,处理完的结果再传给capitalize。注意顺序是从左到右执行的,内容变换的方向别搞反了。

2.2 过滤器和计算属性的分工

初学者经常纠结一个问题:同样的数据处理,用computed和用filter都能实现,到底该选哪个?我自己的实践经验是:如果处理结果要绑定给多个属性、或者要参与其他计算逻辑,用computed;如果纯粹是模板展示环节的格式转换,用filter。

举个例子,商品价格可能是1299,页面要显示¥1,299.00。如果你在多个地方用到这个格式化结果——列表页、详情页、购物车——那我会倾向于写成过滤器,因为它是纯粹的展示格式化,不参与任何业务计算:

<p>{{ price | formatPrice }}</p>

但如果你要根据价格是否超过 1000 来展示不同等级的优惠标签,那建议用computed来派生:

computed: { priceLevel() { if (this.price >= 1000) return 'high'; if (this.price >= 500) return 'mid'; return 'low'; } }

核心判断标准就一条:这个数据变了之后,是会影响到多个视图的展示格式,还是会影响一段业务逻辑?格式问题交给过滤器,逻辑问题交给计算属性。

2.3 过滤器的能力边界

过滤器不是万能的。Vue 2 官方明确规定,过滤器只能用在插值和v-bind表达式中,不能用在v-model上,不能用在指令的指令参数里,也不能在 JS 逻辑代码中直接调用。如果你想在组件的methods里复用某个格式化逻辑,正确做法是把核心逻辑单独抽成一个函数,然后过滤器和 methods 都引用它:

// utils/format.js export function formatPrice(value) { return `¥${Number(value).toFixed(2)}`; } // 组件内 import { formatPrice } from '../utils/format'; filters: { formatPrice }, methods: { handleCheckout() { const displayPrice = formatPrice(this.price); } }

单纯依赖过滤器做所有格式化,很容易遇到场景无法覆盖的时候。把逻辑抽离出来再复用,才是更健康的代码组织方式。

3. 从入门到实战:过滤器的注册与参数传递

3.1 全局过滤器注册

全局过滤器通过Vue.filter()注册,注册后所有组件实例都能使用,不需要在组件里重复定义。

// main.js Vue.filter('formatDate', (value, format = 'YYYY-MM-DD') => { if (!value) return ''; const date = new Date(value); const pad = n => String(n).padStart(2, '0'); if (format === 'YYYY-MM-DD') { return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; } return date.toISOString(); });

全局过滤器适合高频通用场景:日期格式化、金额千分位、字符串截断、文本清洗。我通常把全局过滤器集中放在一个filters/index.js文件里统一管理:

// filters/index.js import Vue from 'vue'; const filters = { formatDate: ..., formatPrice: ..., trimText: ..., upperFirst: ... }; Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]); });

在入口文件直接import './filters'即可,不用在组件里逐个引入,非常省事。

3.2 组件内过滤器注册

组件内过滤器只在当前组件中生效,注册方式是在组件的filters选项中定义。和全局过滤器相比,组件内过滤器的优势是隔离性好,不会污染全局命名空间。如果某个格式化逻辑只会在这一个组件里用到,就别注册全局的,写在组件内部更干净:

export default { data() { ... }, filters: { fileSize(bytes) { if (!bytes) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`; } } }

文件大小格式化、性别数字转文字、枚举状态转标签类名这种场景,特别适合组件内过滤器。它们往往和当前业务强相关,复用性不高,全局注册反而增加认知负担。

3.3 参数传递:过滤器的高级用法

过滤器的第一个参数永远是管道符左边的值。如果要传递额外参数,直接写在过滤器名后面,用括号传参即可。

<p>{{ amount | toFixed(2) }}</p> <p>{{ content | sliceText(0, 20, '...') }}</p>

对应的过滤器函数定义里,value是管道符号前的数据,后面的参数依次填入:

filters: { toFixed(value, digits) { return Number(value).toFixed(digits); }, sliceText(value, start, end, ellipsis = '...') { if (!value) return ''; if (value.length <= end) return value; return value.slice(start, end) + ellipsis; } }

多条参数管道组合使用也很常见,但要注意别过度封装。我见过有人写出一行五六个过滤器串在一起的模板,排查问题简直噩梦:

<p>{{ rawData | filterA | filterB(1, 2) | filterC | filterD }}</p>

这种代码第一时间看不出每一级到底做了什么,出错时只能一级一级去查。建议一个模板里的过滤器链控制在两三个以内,如果逻辑确实复杂,老老实实抽到computed里吧。

3.4 过滤器中的this指向陷阱

一个高频踩坑点:过滤器函数内部的this不是当前组件实例。Vue 2 源码中过滤器函数是直接调用的,不绑定组件上下文。所以你在过滤器里写this.someData,拿不到想要的东西。

filters: { formatWithConfig(value) { // 这里 this 不是组件实例! return `${this.prefix}${value}`; } }

如果你确实需要在过滤器中依赖组件上下文的数据(比如根据当前用户权限决定展示格式),有几个替代方案:

  • 把依赖数据作为参数传入过滤器
  • 模板中直接传this.xxx对应的变量(注意模板中可直接用prefix)
  • 改用computed或methods

正确写法:

<p>{{ content | formatWithConfig }}</p>

配合:

filters: { formatWithConfig(value, prefix) { return `${prefix}${value}`; } }

模板里改成:

<p>{{ content | formatWithConfig(prefix) }}</p>

这样数据从外部传入,过滤器保持纯函数特性,测试和维护都更舒服。

4. 过滤器、串联过滤器的链式调用与性能思考

4.1 链式调用的执行顺序

首先把执行顺序彻底说透。对于:

<p>{{ text | filterA | filterB }}</p>

执行流程是:

1. text 作为参数调用 filterA,得到 resultA 2. resultA 作为参数调用 filterB,得到 resultB 3. resultB 输出到模板

顺序是从左到右。有些人习惯反过来理解,看到管道符就想到后面的数据处理前面的,这是误区。实际验证一下最直接:写两个过滤器,一个加字符串前缀PRE_,一个添加后缀_SUF,顺序调换后结果完全不同。

4.2 过滤器性能开销的真实情况

过滤器在渲染期间执行,意味着组件任何响应式数据变化导致重新渲染,过滤器全部重新跑一遍。哪怕过滤器依赖的数据完全没变,也一样重新执行。这在列表数据很大的场景下会产生明显卡顿。

我在一个近千行的表格页里踩过这个坑。每行五六列都用了全局过滤器做格式化,过滤器中还有正则匹配。当表格数据更新时,整页渲染耗时肉眼可见地增加。后来优化方案是:把格式化逻辑迁移到computed里,在数据进入表格前就处理好:

computed: { formattedRows() { return this.rows.map(row => ({ ...row, orderStatusText: this.statusText(row.orderStatus), priceText: this.formatPrice(row.price), dateText: this.formatDate(row.createdAt) })); } }

循环里一次性处理好所有格式化,模板里只做简单绑定,渲染性能和可追踪性都提升不少。过滤器更推荐用在单一、轻量格式化的场景,复杂数据处理交给computed或后端完成。

4.3 数据清洗:过滤器的隐藏用法

除了展示格式化,过滤器还可以承担一部分数据清洗工作。比如接口返回的富文本里经常带着乱七八糟的标签,直接v-html有 XSS 风险,配合一个清洗过滤器会好很多:

<div v-html="content | sanitizeHtml"></div>
sanitizeHtml(value) { if (!value) return ''; // 这里建议用成熟库替代简单正则,比如 DOMPurify return value.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ''); }

但必须强调:过滤器不能替代真正的安全防护。过滤器的输出是在客户端做展示处理,真正的 XSS 防护必须由后端在输入阶段做严格校验,前端过滤器只是兜底措施。尤其是涉及v-html的场景,不要只用简单的字符串替换就完事,否则等于把安全防线漏了个大洞。

这种“清洗型过滤器”用好了体验很好。比如用户输入的手机号展示时需要脱敏:

<p>{{ phone | maskPhone }}</p>
maskPhone(value) { if (!value || value.length < 7) return value; return value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); }

本质上,过滤器就是一个“展示前管道处理器”,做格式化、脱敏、清洗都是它的适合领地。

5. 必须避开的五个大坑与排除实践

5.1 滤器名称冲突的坑

全局过滤器和组件内过滤器允许同名,同名时优先使用组件内过滤器。这个规则本身不复杂,但多人协作时很容易踩坑。比如说全局定义了一个formatDate,某个组件内又定义了同名但逻辑不同的formatDate,排查 bug 时极难发现——你以为是全局逻辑,实际被组件覆盖了。

规范建议:组件内过滤器命名时,前缀加上组件名或业务前缀,例如orderStatusText、userPhoneMask,尽量避免和全局过滤器同名。同时,新增过滤器之前先在filters/index.js里扫一眼,确认不存在同名情况。

5.2 过滤器里改传入参数的坑

JavaScript 中对象和数组是引用传递。如果你在过滤器里对传入的对象参数做了修改,会直接改动原始数据。

filters: { addLabel(item) { item.label = '新标签'; // 这里改了原始对象! return item; } }

模板渲染时原始对象被污染,可能导致其他依赖该数据的地方出现难以预料的 bug。过滤器应当是纯函数——相同输入永远是相同输出,且不修改外部状态。需要返回修改后的值,先拷贝再操作:

filters: { addLabel(item) { const copy = { ...item, label: '新标签' }; return copy; } }

5.3 日期格式化时区问题

这是国际化项目里常见的坑。后端返回时间戳或 ISO 字符串,前端直接new Date(value)后格式化,会遇到时区偏移的问题。比如某后端返回2024-06-01T16:00:00.000Z(UTC 时间),在 UTC+8 时区下本地显示是2024-06-02 00:00:00。直接按getFullYear()取,取到的是本地时区的日期,可能是第二天。

处理方案:过滤器中明确指定使用哪个时区和格式,或者后端统一返回已经处理好的本地时间字符串。如果必须前端处理,最好借助成熟的日期库(注意:项目需自行评估依赖风险和许可证,适合场景再引入),自己徒手实现 UTC 转本地且处理夏令时之类的问题,代码会变得非常不可控。

5.4 Vue 3 中过滤器被移除的迁移问题

如果你在做 Vue 3 升级,这里有个必须提前知道的点:Vue 3 官方移除了过滤器。原因是从设计上看,过滤器不够“显性”,且已有计算属性和方法可以覆盖它的应用场景。

迁移方案有两个:

方案一:替换为方法(methods)

<p>{{ formatDate(date) }}</p>
methods: { formatDate(value) { // ... } }

方案二:替换为计算属性(computed)

这种方法适合格式化逻辑依赖响应式数据且会被多处引用的场景:

computed: { formattedDate() { return this.formatDate(this.date); } }

如果项目里用了大量过滤器,我建议先写个扫描脚本把| filterName全部找出来,逐个替换。尤其注意:在 Vue 3 中模板里存在|管道符号,不会报错,但会被当作非法表达式直接编译失败。所以升级前做一次全局检索很关键。

还有一个过渡用的思路,有的团队会在 Vue 3 项目中通过自定义指令或混入来模拟过滤器,但我个人不建议这么做——绕了一圈只是维持旧习惯,最终还是要学会用方法或计算属性思维来组织代码。直接一步到位迁移到 methods/computed 更省钱。

5.5 忘记注册直接使用的坑

模板里写了{{ value | myFilter }},但忘了在filters里定义,或者全局注册里没写上。报错信息通常是:

Failed to resolve filter: myFilter

排查思路很简单:先在源码里全局搜myFilter,确认是否存在于filters/index.js或组件filters选项里。如果是按需加载的模块化结构,还要确认当前组件是否真的 import 了对应文件。这种问题排查起来不难,但每次出现都会浪费不少时间,最好在代码提交前做一次模板过滤器的完整检索。

6. 一套完整的序化方案:从真实项目中提炼

6.1 场景回顾

我曾经参与维护过一个中等规模的后台管理前端,技术栈是 Vue 2 + Element UI。页面涉及订单、用户、商品、营销等多个模块,格式化需求非常多:状态码转文案、金额千分位、时间戳转日期、手机号脱敏、文本截断、枚举转标签颜色等等。早期代码里每个组件都零散地定义着自己的格式化方法,同一个“创建时间格式化”逻辑在五个组件里复制了五份。后来一次重构,我们把这块系统性地整理了一遍。

6.2 分层设计思路

最终方案分三层:

第一层:全局通用格式化逻辑(工具函数层)

放在utils/format.js,是纯函数集合,不依赖 Vue 任何特性,方便在任意 JS 文件中调用和单元测试:

// utils/format.js export function formatPrice(value, currency = '¥') { if (value === null || value === undefined) return `--`; return `${currency}${Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } export function formatDateTime(value) { if (!value) return ''; const d = new Date(value); // ... } export function maskPhone(phone) { // ... }

第二层:全局过滤器注册层

把工具函数包装成过滤器,在filters/index.js统一注册。工具函数和过滤器一一对应,函数名保持统一,避免隐藏映射:

// filters/index.js import Vue from 'vue'; import * as formatters from '../utils/format'; const filterMap = { formatPrice: formatters.formatPrice, formatDateTime: formatters.formatDateTime, maskPhone: formatters.maskPhone }; Object.keys(filterMap).forEach(name => { Vue.filter(name, filterMap[name]); });

第三层:组件内局部过滤器

业务强绑定的格式化逻辑(比如“订单状态徽标类名、用户类型标签文案”这类只在该模块使用的),写在组件内filters中,同样建议从工具函数引入,而不是直接在过滤器里写复杂逻辑:

// 组件内 import { formatDateTime } from '../utils/format'; export default { filters: { orderTime: formatDateTime, statusBadge(status) { const map = { pending: 'warning', paid: 'success', shipped: 'primary' }; return map[status] || 'info'; } } };

6.3 模板中的调用规范

我们对设计规范和命名规范的约束如下:

  • 过滤器命名一律采用驼峰式,语义清晰,避免缩写如fmt、t之类的。
  • 串联过滤器超过 2 个,强制抽到computed。
  • 过滤器内部禁止修改传入参数。
  • 过滤器内部禁止访问this,依赖外部状态时改用参数传入。
  • 新增加过滤器必须同步补充对应的工具函数和基本注释。

模板效果:

<template> <div> <p>成交金额:{{ order.amount | formatPrice }}</p> <p>下单时间:{{ order.createdAt | formatDateTime }}</p> <p>联系手机:{{ order.phone | maskPhone }}</p> <el-tag :type="order.status | statusBadge"> {{ order.status | statusText }} </el-tag> </div> </template>

这样模板核心只表达“展示什么”,不再关心“怎么转换”,维护时只需要在一个地方修改。

6.4 对既有项目的渐进式改造

做完这套整理后,我总结出一个对旧项目友好的迁移节奏:

  1. 先建立utils/format.js,把项目中重复的格式化逻辑收敛成纯函数。
  2. 再建立filters/index.js,用最常用的几个全局过滤器替换模板中高频出现的格式化调用。
  3. 最后逐个页面清理组件内重复的格式化代码,能复用全局的就不复制粘贴。

这个过程不需要大版本重构,每走一步都能正常上线,风险逐个隔离。对于已经用了很久的 Vue 2 项目来说,这是最平缓的演进方式。

7. 做一个决策可行的总结:过滤器用不用、怎么用

聊了这么多,最后梳理一下我个人的核心判断,方便你在实际项目里快速做决策。

过滤器适合解决的场景:

  • 简单格式转换:日期、金额、文本截断。
  • 高频内容脱敏:手机号、身份证、邮箱。
  • 枚举状态码转展示文案。
  • 模板插值和v-bind上的小型数据清洗。

不建议用过滤器的场景:

  • 需要依赖组件实例数据的逻辑(用computed)。
  • 逻辑复杂、多个步骤处理的流程(该方法不阻塞的情况下,但用函式方法更清晰)。
  • 数据需要被多处使用的场景(用computed缓存)。
  • 新写的 Vue 3 项目(直接用methods或computed)。

如果你还在维护 Vue 2 项目,过滤器作为一种轻量级模板工具,用得恰当能大幅度提升代码可读性。如果你正准备转向 Vue 3,也不用过度遗憾——把原来写在过滤器里的逻辑迁到methods/computed中,代码表达实际上更直白。我自己做了几次替换之后反而觉得,强制不用过滤器,逼着你把格式化逻辑提到组件层,虽然之前需要改动的地方多一些,但整体代码结构确实更加清晰可测了。

最后分享一个小习惯:我现在每写一个过滤器,都会顺手在旁边写两三个测试用例。过滤器是纯函数,测试成本极低,效果却立竿见影。遇到时区问题、边界值为空之类的情况,写完测试的时候已经能提前排掉一半的坑。如果你还没有这个习惯,下次写过滤器时尝试加上,应该能感受到明显的好处。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 1:04:47

MCU的职责边界:STM32+ESP8266+云平台应用范围划分实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:03:56

用IDL脚本实现ENVI GLT重投影:原理、步骤与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:03:54

双电芯主动均衡方案:MP2642GR-Z与PIC18F4685工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:03:15

RecyclerView Adapter三态管理:空布局、错误页与尾部提示一体化方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 1:03:09

零代码抓取滚动加载网页:点几下鼠标搞定数据采集

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华