Vue项目中Collection模式方法首次调用正常,二次调用报错非函数
问题分析与解决方案
问题根源
你的代码中,ExerciseCollection通过给原生数组挂载自定义方法后返回数组,但Vue 3在处理data中的数组时,会自动将其包装成响应式Proxy对象。这个代理只会保留原生数组的方法(如push、splice),你手动挂载的add、delete等自定义方法不会被代理继承。
首次调用add时,数组还未完全被Vue的响应式系统处理,方法存在;第二次调用时,数组已经变成了响应式Proxy,自定义方法丢失,因此抛出add is not a function错误。
另外,原集合代码还存在逻辑错误:this.indexOf(exercise)返回的是元素索引(数字),而非元素对象,所以editExercise.weight.splice(set, 1)这类代码会直接报错,因为数字没有weight属性。
修复方案:改用类实现集合
最可靠的方式是用ES6类来封装集合逻辑,类实例的方法不会被Vue的响应式代理破坏,同时能修正原有的逻辑错误:
1. 重写ExerciseCollection类
class ExerciseCollection { constructor(arr = []) { // 用items存储数组,避免直接操作原型 this.items = arr; } add(exercise, date) { if (date) { exercise.date = date; } exercise.id = genId(date); this.items.push(exercise); return this; } delete(exercise) { const index = this.items.indexOf(exercise); if (index !== -1) { this.items.splice(index, 1); } return this; } deleteSet(exercise, set) { // 通过id查找目标元素,比indexOf更可靠(避免引用变化导致找不到) const targetExercise = this.items.find(item => item.id === exercise.id); if (targetExercise) { targetExercise.weight.splice(set, 1); targetExercise.reps.splice(set, 1); targetExercise.sets--; } return this; } addSet(exercise) { const targetExercise = this.items.find(item => item.id === exercise.id); if (targetExercise) { targetExercise.sets++; } return this; } }
2. 修改Vue组件中的初始化代码
const app = Vue.createApp({ data() { return { // 初始化集合并添加初始数据 exerciseList: new ExerciseCollection().add({ id: 0, title: 'Bench Press', date: '2024-04-03', sets: 2, reps: [10, 10], weight: [185, 185] }), dayList: new DayExerciseCollection(), reviewList: new ReviewCollection(), daysReview:{}, selectedEditExercise: {}, currentDay: '' }; }, methods: { addExercise(exercise, date){ // 调用类实例的add方法 this.exerciseList.add(exercise, date); this.dayList.add(exercise, date); }, // 其他方法... } });
为什么这个方案有效?
- 类实例的方法属于实例本身,Vue的响应式代理会完整保留这些方法,不会丢失。
- 用
items属性存储数组,避免直接修改原生数组的原型,减少冲突风险。 - 改用
find通过id查找元素,比indexOf更可靠(如果元素引用变化,indexOf会找不到,但id是唯一标识)。
额外说明
如果一定要基于原生数组扩展,也可以将自定义方法添加到数组原型,但这种方式会污染全局数组,不推荐:
// 不推荐:污染全局数组原型 Array.prototype.exerciseAdd = function(exercise, date) { if (date) exercise.date = date; exercise.id = genId(date); this.push(exercise); return this; }
内容的提问来源于stack exchange,提问作者JosefGungl
相关产品推荐
相关产品推荐

