哈喽,大家好!我是今天的主讲人,很高兴能和大家一起聊聊 Vue 响应式系统和数据筛选排序组件。今天咱们不搞那些虚头巴脑的理论,直接上干货,手把手带你撸一个可配置的数据筛选和排序组件。
开场白:为啥要折腾这个?
话说,咱们前端开发,天天跟数据打交道。表格、列表,哪个项目离得开?数据一多,筛选排序就成了刚需。如果每次都手动写,那得累死。所以,一个灵活、可配置的筛选排序组件,绝对是提高生产力的神器!
第一部分:Vue 响应式系统:咱们的基石
要实现一个好的筛选排序组件,首先得理解 Vue 的响应式系统。简单来说,它就像一个超级侦察兵,时刻监视着你的数据,一旦数据发生变化,它就能迅速通知相关的组件进行更新。
- 响应式原理: Vue 内部使用了
Object.defineProperty
(Vue 2) 或Proxy
(Vue 3) 来劫持数据的 getter 和 setter。当你在模板中使用数据时,Vue 会自动追踪这些数据的依赖关系。当数据发生变化时,Vue 会通知所有依赖该数据的组件进行重新渲染。 data
选项: 这是定义组件数据的关键。所有在data
中声明的属性,都会被 Vue 转化为响应式属性。computed
属性: 用于声明基于其他响应式属性计算得出的属性。computed
属性具有缓存机制,只有当依赖的属性发生变化时,才会重新计算。watch
监听器: 用于监听特定响应式属性的变化。当属性发生变化时,可以执行自定义的回调函数。
第二部分:组件架构设计:搭好积木的框架
咱们的组件,要足够灵活,可以应对各种不同的数据结构和筛选排序需求。所以,要好好设计一下。
- 核心组件: 咱们创建一个名为
DataFilterSorter
的核心组件。这个组件负责接收数据、配置项,并根据配置项进行筛选和排序。 - 配置项: 配置项是灵魂!它决定了组件的行为。配置项可以包括:
fields
: 一个数组,包含需要筛选和排序的字段信息。sortable
: 一个布尔值,指示是否允许排序。filterable
: 一个布尔值,指示是否允许筛选。filters
: 一个对象,包含初始的筛选条件。sort
: 一个对象,包含初始的排序字段和方向。
- 数据流: 数据从父组件传递给
DataFilterSorter
组件。DataFilterSorter
组件根据配置项对数据进行处理,并将处理后的数据传递给子组件进行展示。
第三部分:代码实现:撸起袖子开干
现在,咱们开始写代码。
-
创建
DataFilterSorter.vue
组件:<template> <div> <!-- 筛选表单 --> <div v-if="filterable"> <form @submit.prevent="applyFilters"> <div v-for="field in fields" :key="field.key"> <label :for="field.key">{{ field.label }}:</label> <input type="text" :id="field.key" v-model="filterValues[field.key]" /> </div> <button type="submit">筛选</button> <button type="button" @click="resetFilters">重置</button> </form> </div> <!-- 排序表头 --> <div v-if="sortable"> <table> <thead> <tr> <th v-for="field in fields" :key="field.key" @click="sortBy(field.key)"> {{ field.label }} <span v-if="sort.field === field.key"> {{ sort.direction === 'asc' ? '▲' : '▼' }} </span> </th> </tr> </thead> <tbody> <tr v-for="item in filteredAndSortedData" :key="item.id"> <td v-for="field in fields" :key="field.key">{{ item[field.key] }}</td> </tr> </tbody> </table> </div> <!-- 如果不需要筛选和排序,直接展示数据 --> <div v-else> <table> <thead> <tr> <th v-for="field in fields" :key="field.key">{{ field.label }}</th> </tr> </thead> <tbody> <tr v-for="item in data" :key="item.id"> <td v-for="field in fields" :key="field.key">{{ item[field.key] }}</td> </tr> </tbody> </table> </div> </div> </template> <script> export default { props: { data: { type: Array, required: true, }, fields: { type: Array, required: true, }, sortable: { type: Boolean, default: false, }, filterable: { type: Boolean, default: false, }, filters: { type: Object, default: () => ({}), }, sort: { type: Object, default: () => ({ field: null, direction: 'asc', }), }, }, data() { return { filterValues: { ...this.filters }, // 使用浅拷贝,避免直接修改父组件的filters sortData: { ...this.sort } //同样使用浅拷贝 }; }, computed: { filteredData() { if (!this.filterable) { return this.data; } return this.data.filter((item) => { for (const key in this.filterValues) { if (this.filterValues[key] && String(item[key]).toLowerCase().indexOf(this.filterValues[key].toLowerCase()) === -1) { return false; } } return true; }); }, filteredAndSortedData() { let data = [...this.filteredData]; // Create a copy to avoid modifying the original data if (this.sortable && this.sortData.field) { data.sort((a, b) => { const field = this.sortData.field; const direction = this.sortData.direction; let comparison = 0; if (a[field] > b[field]) { comparison = 1; } else if (a[field] < b[field]) { comparison = -1; } return direction === 'asc' ? comparison : -comparison; }); } return data; }, }, methods: { applyFilters() { // 这里可以做一些验证,或者触发事件通知父组件 this.$emit('update:filters', this.filterValues); // 通知父组件更新filters }, resetFilters() { for (let key in this.filterValues) { this.filterValues[key] = ''; } this.$emit('update:filters', this.filterValues); // 通知父组件更新filters }, sortBy(field) { if (!this.sortable) { return; } if (this.sortData.field === field) { this.sortData.direction = this.sortData.direction === 'asc' ? 'desc' : 'asc'; } else { this.sortData.field = field; this.sortData.direction = 'asc'; } this.$emit('update:sort', this.sortData); }, }, }; </script>
-
父组件中使用
DataFilterSorter
组件:<template> <div> <DataFilterSorter :data="myData" :fields="myFields" :sortable="true" :filterable="true" :filters="myFilters" :sort="mySort" @update:filters="updateFilters" @update:sort="updateSort" /> </div> </template> <script> import DataFilterSorter from './DataFilterSorter.vue'; export default { components: { DataFilterSorter, }, data() { return { myData: [ { id: 1, name: '张三', age: 25, city: '北京' }, { id: 2, name: '李四', age: 30, city: '上海' }, { id: 3, name: '王五', age: 28, city: '广州' }, { id: 4, name: '赵六', age: 22, city: '深圳' }, ], myFields: [ { key: 'name', label: '姓名' }, { key: 'age', label: '年龄' }, { key: 'city', label: '城市' }, ], myFilters: { name: '', age: '', city: '' }, mySort: { field: 'age', direction: 'desc' } }; }, methods: { updateFilters(newFilters) { this.myFilters = newFilters; }, updateSort(newSort) { this.mySort = newSort; } } }; </script>
代码解释:
DataFilterSorter.vue
组件:props
: 定义了组件接收的配置项,包括数据、字段信息、是否允许筛选和排序等。data
: 定义了组件内部的状态,包括筛选条件和排序信息。computed
: 定义了计算属性,用于根据筛选条件和排序信息对数据进行处理。filteredData
: 根据筛选条件过滤数据。这里使用了String(item[key]).toLowerCase().indexOf(this.filterValues[key].toLowerCase()) === -1
来进行模糊匹配,并且忽略大小写。filteredAndSortedData
: 先过滤数据,然后根据排序信息对数据进行排序。这里使用了sort
方法进行排序,并根据sort.direction
来决定升序还是降序。
methods
: 定义了组件的方法,包括应用筛选、重置筛选、排序等。applyFilters
: 应用筛选条件。这里可以做一些验证,或者触发事件通知父组件。resetFilters
: 重置筛选条件。sortBy
: 根据指定的字段进行排序。如果已经按照该字段排序,则切换排序方向;否则,按照该字段升序排序。
- 父组件:
- 引入
DataFilterSorter
组件。 - 定义了
myData
、myFields
、myFilters
和mySort
等数据,用于配置DataFilterSorter
组件。 - 使用了
@update:filters
和@update:sort
来监听DataFilterSorter
组件触发的事件,并更新父组件的状态。 使用了:filters.sync="myFilters"
:sort.sync="mySort"
是vue2的语法,vue3使用@update:filters="updateFilters"
@update:sort="updateSort"
来代替.
- 引入
第四部分:进阶技巧:让组件更上一层楼
- 自定义筛选器: 可以允许用户自定义筛选器,例如使用日期范围选择器、下拉选择器等。这可以通过在
fields
配置项中添加type
属性来实现。 - 服务端排序: 当数据量非常大时,前端排序可能会影响性能。可以考虑将排序逻辑放在服务端,前端只需要传递排序字段和方向即可。
- 可访问性: 确保组件具有良好的可访问性,例如使用适当的 ARIA 属性。
- 单元测试: 编写单元测试,确保组件的稳定性和可靠性。
第五部分:总结:好记性不如烂笔头
咱们今天一起撸了一个可配置的数据筛选和排序组件,核心是利用 Vue 的响应式系统,根据配置项动态地对数据进行处理。
组件的核心功能和特点:
功能 | 描述 |
---|---|
数据接收 | 通过 props 接收父组件传递的数据 (data ) 和字段配置信息 (fields )。 |
灵活配置 | 通过 props 接收 sortable 和 filterable 属性,控制组件是否启用排序和筛选功能。 |
初始状态 | 通过 props 接收 filters 和 sort 属性,设置组件的初始筛选条件和排序状态。 |
响应式筛选 | 使用 computed 属性 filteredData ,根据 filterValues 响应式地过滤数据。 筛选条件通过双向绑定到表单输入框,实现实时筛选。 |
响应式排序 | 使用 computed 属性 filteredAndSortedData ,在筛选后的数据基础上,根据 sort.field 和 sort.direction 响应式地排序数据。 |
用户交互 | 提供筛选表单和重置按钮,允许用户输入筛选条件。 通过点击表头触发排序,切换排序字段和方向。 |
事件通信 | 通过 $emit 触发 update:filters 和 update:sort 事件,将最新的筛选条件和排序状态传递给父组件,实现父子组件之间的双向数据绑定。 |
浅拷贝 | 使用浅拷贝 filterValues: { ...this.filters } 和 sortData: { ...this.sort } ,避免直接修改父组件传递的 filters 和 sort 属性。 |
模糊匹配 | 筛选时使用 String(item[key]).toLowerCase().indexOf(this.filterValues[key].toLowerCase()) === -1 进行模糊匹配,并且忽略大小写。 |
避免修改原数据 | 在排序前使用 let data = [...this.filteredData] 创建数据的副本,避免修改原始数据。 |
记住,代码不是一蹴而就的,需要不断地练习和改进。希望今天的分享能帮助你更好地理解 Vue 响应式系统,并能够应用到实际项目中。
下次再见!