Vue 过滤器:优雅处理数据的艺术
在现代前端开发中,数据展示的格式化是一个高频需求。Vue 过滤器提供了一种优雅且可复用的解决方案,让我们的模板代码更加清晰简洁。
什么是 Vue 过滤器?
Vue 过滤器是一种特殊的函数,用于对数据进行格式化处理。它们可以在模板插值 和 v-bind 表达式 中使用,通过管道符 | 连接。
vue
<!-- 基本使用 -->
<template>
<div>
<!-- 文本插值 -->
<p>{{ message | capitalize }}</p>
<!-- 在 v-bind 中 -->
<div :title="message | capitalize"></div>
<!-- 链式调用 -->
<p>{{ price | currency | uppercase }}</p>
<!-- 传参 -->
<p>{{ date | formatDate('YYYY-MM-DD') }}</p>
</div>
</template>
过滤器的定义方式
1. 局部过滤器
在组件选项中定义,仅在当前组件内可用:
javascript
export default {
data() {
return {
price: 99.99,
date: '2024-01-15'
}
},
filters: {
// 简单过滤器
currency(value) {
if (typeof value !== 'number') return value
return '¥' + value.toFixed(2)
},
// 带参数的过滤器
formatDate(value, format = 'YYYY-MM-DD HH:mm') {
if (!value) return ''
const date = new Date(value)
// 简化的格式化逻辑,实际项目中建议使用 date-fns 或 dayjs
if (format === 'YYYY-MM-DD') {
return date.toISOString().split('T')[0]
}
return date.toLocaleString()
}
}
}
2. 全局过滤器
在 Vue 实例创建前定义,可在所有组件中使用:
javascript
// main.js 或独立的 filters.js 文件
import Vue from 'vue'
// 货币格式化
Vue.filter('currency', function(value) {
if (typeof value !== 'number') return value
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
minimumFractionDigits: 2
}).format(value)
})
// 文本截断
Vue.filter('truncate', function(value, length = 20, suffix = '...') {
if (!value || typeof value !== 'string') return value
if (value.length <= length) return value
return value.substring(0, length) + suffix
})
过滤器的核心应用场景
1. 文本格式化
javascript
// 常见文本处理过滤器
Vue.filter('capitalize', value => {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
Vue.filter('uppercase', value => {
if (!value) return ''
return value.toString().toUpperCase()
})
Vue.filter('lowercase', value => {
if (!value) return ''
return value.toString().toLowerCase()
})
2. 数字与货币处理
javascript
// 数字格式化
Vue.filter('number', (value, decimals = 0) => {
if (typeof value !== 'number') return value
return new Intl.NumberFormat('zh-CN', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(value)
})
// 百分比
Vue.filter('percent', (value, decimals = 1) => {
if (typeof value !== 'number') return value
return (value * 100).toFixed(decimals) + '%'
})
// 文件大小
Vue.filter('fileSize', bytes => {
if (typeof bytes !== 'number') return bytes
const units = ['B', 'KB', 'MB', 'GB', 'TB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `${size.toFixed(1)} ${units[unitIndex]}`
})
3. 日期时间处理
javascript
// 日期格式化(建议集成 date-fns 或 dayjs)
import { format } from 'date-fns'
Vue.filter('date', (value, pattern = 'yyyy-MM-dd') => {
if (!value) return ''
try {
const date = new Date(value)
return format(date, pattern)
} catch (e) {
return value
}
})
// 相对时间(如:3小时前)
Vue.filter('relativeTime', value => {
if (!value) return ''
const date = new Date(value)
const now = new Date()
const diffInSeconds = Math.floor((now - date) / 1000)
const intervals = {
年: 31536000,
月: 2592000,
周: 604800,
天: 86400,
小时: 3600,
分钟: 60,
秒: 1
}
for (const [unit, seconds] of Object.entries(intervals)) {
const interval = Math.floor(diffInSeconds / seconds)
if (interval >= 1) {
return `${interval}${unit}前`
}
}
return '刚刚'
})
4. 业务数据转换
javascript
// 状态映射
Vue.filter('orderStatus', value => {
const statusMap = {
'pending': '待处理',
'processing': '处理中',
'shipped': '已发货',
'delivered': '已送达',
'cancelled': '已取消'
}
return statusMap[value] || value
})
// 掩码处理(如手机号、身份证)
Vue.filter('mask', (value, start = 3, end = 4, maskChar = '*') => {
if (!value || typeof value !== 'string') return value
if (value.length <= start + end) return value
const visibleStart = value.substring(0, start)
const visibleEnd = value.substring(value.length - end)
const maskLength = value.length - start - end
return visibleStart + maskChar.repeat(maskLength) + visibleEnd
})
// 数组转换为字符串
Vue.filter('join', (value, separator = ', ') => {
if (!Array.isArray(value)) return value
return value.join(separator)
})
进阶技巧与实践
1. 过滤器组合与链式调用
vue
<template>
<div>
<!-- 链式调用:先格式化日期,再转换为相对时间 -->
<p>{{ createdTime | date('yyyy-MM-dd HH:mm') | relativeTime }}</p>
<!-- 多个参数传递 -->
<p>{{ phoneNumber | mask(3, 4, '*') }}</p>
<!-- 与计算属性结合 -->
<p>{{ formattedAmount }}</p>
</div>
</template>
<script>
export default {
data() {
return {
createdTime: '2024-01-15T10:30:00',
phoneNumber: '13800138000',
amount: 123456.789
}
},
computed: {
formattedAmount() {
// 在计算属性中使用 this.$options.filters 访问过滤器
const currencyFilter = this.$options.filters.currency
return currencyFilter ? currencyFilter(this.amount) : this.amount
}
}
}
</script>
2. 性能优化:避免在循环中使用复杂过滤器
vue
<template>
<!-- 不推荐:每次循环都会执行过滤器 -->
<div v-for="item in items" :key="item.id">
{{ item.price | complexFilter }}
</div>
<!-- 推荐:预处理数据 -->
<div v-for="item in processedItems" :key="item.id">
{{ item.formattedPrice }}
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, price: 99.99 },
{ id: 2, price: 199.99 }
]
}
},
computed: {
processedItems() {
return this.items.map(item => ({
...item,
formattedPrice: this.$options.filters.currency(item.price)
}))
}
}
}
</script>
Vue 2 与 Vue 3 的差异
Vue 2
过滤器是核心功能,使用方式如上所述。
Vue 3
在 Vue 3 中,过滤器已被移除,官方建议以下替代方案:
vue
<!-- Vue 3 替代方案 -->
<template>
<!-- 使用计算属性 -->
<p>{{ formattedDate }}</p>
<!-- 使用方法调用 -->
<p>{{ formatDate(date) }}</p>
<!-- 使用全局方法 -->
<p>{{ $filters.currency(price) }}</p>
</template>
<script>
// 方法1:计算属性
export default {
computed: {
formattedDate() {
return this.formatDate(this.date)
}
},
methods: {
// 方法2:组件方法
formatDate(value) {
// 格式化逻辑
}
}
}
// 方法3:全局属性
app.config.globalProperties.$filters = {
currency(value) {
// 货币格式化逻辑
}
}
</script>
最佳实践与注意事项
- 单一职责原则:每个过滤器只做一件事
- 错误处理:始终考虑输入值的边界情况
- 国际化支持:为多语言环境设计可配置的过滤器
- 性能考量:避免在大型列表中使用复杂过滤器
- 测试覆盖:为业务关键过滤器编写单元测试
javascript
// 带有完整错误处理的过滤器示例
Vue.filter('safeCurrency', value => {
try {
if (value == null || value === '') return '--'
if (typeof value === 'string') value = parseFloat(value)
if (typeof value !== 'number' || isNaN(value)) return '--'
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value)
} catch (error) {
console.warn('Currency filter error:', error)
return '--'
}
})
总结
Vue 过滤器为数据格式化提供了一种声明式、可复用的解决方案。虽然 Vue 3 中已移除了过滤器功能,但在 Vue 2 项目中,合理使用过滤器可以显著提升代码的可读性和维护性。即使迁移到 Vue 3,过滤器的设计思想------关注点分离和逻辑复用------仍然值得我们借鉴。
过滤器不是万能工具,但在合适的场景下,它们能让我们的 Vue 应用更加优雅和高效。在选择使用过滤器还是其他方案时,关键在于考虑项目的具体需求、团队习惯以及未来的可维护性。
思考题:在你的项目中,哪些数据处理逻辑最适合用过滤器(或类似方案)来实现?欢迎在评论区分享你的实践经验!