前言
📫 大家好,我是陈三心,热爱技术和分享,欢迎大家交流,一起学习进步!
🍅 个人主页:陈三心
Table表格在前端开发中是十分常见的,用于展示结构化数据,如商品列表、用户信息等。
本文分享一下自己在表格开发中遇到的一个奇葩问题,以及采取的解决方法。
目录
背景
编辑数量
极端情况
闭包
结语
背景
项目中使用Ant Design Vue进行表格的开发,允许用户直接在表格内编辑数量,如下:
上述就是本次任务需要实现的目标,是的,你没听错,就是这么简单。但其中却有着一个意想不到的坑,听我细细道来。
编辑数量
首先当然就是数据的获取了,在组件实例创建后调用接口获取列表数据。
<template>
<a-table
:columns="columns"
:data-source="dataList"
>
</a-table>
</template>
<script>
export default {
data() {
return {
// 设置表格列描述对象
columns: [
...
],
// 表格数据
dataList: [],
}
},
created() {
this.getDataList()
},
methods: {
/*
* 获取数据列表
*/
getDataList() {
//此处省略...
},
},
}
</script>
编辑数量使用Ant Design Vue的数字输入框组件,利用table的scopedSlots进行列的自定义渲染。
<template>
<a-table :columns="columns" :data-source="dataList">
<template slot="productCount" slot-scope="text, record">
<a-input-number
v-model="record.productCount"
@change="editNumber(record)"
></a-input-number>
</template>
</a-table>
</template>
<script>
export default {
...
methods: {
/**
* 编辑数量
* @param {object} row 物品信息
*/
editNumber(row) {
// 发送请求更新数量,更新完后重新获取数据
...
},
},
}
</script>
给数字输入框绑定change事件,只要改变数量就发送请求给后端触发更新。当然,这肯定是有问题的,频繁发送请求会造成性能消耗,所以加上防抖是很有必要的。对上述代码进行优化:
<script>
export default {
...
methods: {
/**
* 编辑数量
* @param {object} row 物品信息
*/
editNumber: debounce(function (row) {
// 发送请求更新数量,更新完后重新获取数据
...
}, 1000),
},
}
</script>
上述debounce可以使用lodash库中的防抖函数,如果不想引入额外的库,也可以自己手写封装,具体可以去看下我的这篇文章。
极端情况
到此,你以为编辑数量的功能就完成了吗?作为一个开发者,我们当然需要考虑各种极端情况,来看看下面这个:
上述场景中,使用上下箭头去修改当前行的数量,编辑完后立马点击另一行的数字输入框上下箭头修改数量,此时却只生效了后一个的数量编辑。
这是什么原因呢?思来想去,不如直接交给GPT来解答。
总结下问题原因其实就是,表格中的每个数字输入框绑定的是同一个debounce函数,共享了相同的debounce定时器。
知道问题原因后,解决思路就很简单了,为每个数字输入框元素创建独立的 debounce 实例,这样debounce的定时器不会在多个输入框间共享,每个元素都有自己的防抖逻辑。改进后的代码:
<template>
<a-table :columns="columns" :data-source="dataList">
<template slot="productCount" slot-scope="text, record, index">
<a-input-number
v-model="record.productCount"
@change="debounceEditNumber(record, index)"
></a-input-number>
</template>
</a-table>
</template>
<script>
export default {
data() {
return {
// 存储每个元素的 debounce 实例
debouncedHandlers: {},
}
},
methods: {
/**
* 为每个元素创建独立的 debounce 实例
* @param {object} row 设备信息
* @param {number} index 行索引
*/
createDebouncedHandler(row, index) {
// 清除已存在的 debounce 实例
if (this.debouncedHandlers[index]) {
this.debouncedHandlers[index].cancel()
}
// 创建新的 debounce 实例
this.debouncedHandlers[index] = debounce(() => {
this.editNumber(row)
}, 1000)
return this.debouncedHandlers[index]
},
/**
* 编辑设备数量
* @param {object} row 物品信息
* @param {number} index 行索引
*/
debounceEditNumber(row, index) {
this.createDebouncedHandler(row, index)()
},
/**
* 更新数量
* @param {object} row 物品信息
*/
editNumber(row) {
// 发送请求更新数量,更新完后重新获取数据
...
},
},
}
</script>
组件实例销毁之前,需要在beforeDestroy中清理所有 debounce 实例,防止内存泄漏。
beforeDestroy() {
// 清理所有 debounce 实例,防止内存泄漏
Object.keys(this.debouncedHandlers).forEach((index) => {
this.debouncedHandlers[index].cancel()
delete this.debouncedHandlers[index]
})
},
现在的效果:
闭包
其实深入思考下,定时器之所以会共享,本质原因是 debounce 函数返回的是一个闭包。以下是debounce的内部实现逻辑:
function debounce(fn, delay = 500) {
let timer = null;
// 这里返回的函数是每次用户实际调用的防抖函数
return function(...args) {
// 如果已经设定过定时器了就清空上一次的定时器
if(timer) {
clearTimeout(timer);
}
// 开始一个新的定时器,延迟执行用户传入的方法
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
使用闭包来保存定时器变量timer,每次实际调用的函数都可以访问到外层作用域中的这个timer,就会导致上述问题(闭包可以去看我的这篇文章)。
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~