Vue中v-for项移除再添加后视图属性不更新问题
Vue 图片编辑撤销/重做功能绑定失效问题
问题场景
正在开发支持产品图片添加及Undo(撤销)、Redo(重做)功能的Vue特性,已简化代码复现问题:
存在ImageItem组件,通过v-for指令渲染,接收imageModel对象类型prop,内部通过textarea绑定并修改该对象的description属性。当列表项被移除后重新添加时,textarea的value属性绑定似乎失效。
复现步骤
- 点击「Add image」按钮,添加带有textarea的行;
- 在textarea中输入图片描述文本;
- 点击「Undo」按钮,撤销描述修改(textarea变为空);
- 再次点击「Undo」按钮,移除图片项;
- 点击「Redo」按钮,恢复图片项(textarea仍为空,符合预期);
- 再次点击「Redo」按钮,此时应恢复描述,但textarea未显示内容,绑定失效。
完整代码(与Vue Playground一致)
App.vue
<script setup> import { ref } from 'vue' import ImageItem from './ImageItem.vue'; import { ProductEditModel } from './ProductEditModel'; const productEditModel = ref(new ProductEditModel()); function addImage() { productEditModel.value.addImage(); } function handleChangeImageDescription(imageModel, description) { productEditModel.value.changeImageDescription(imageModel, description); } function undo() { productEditModel.value.editHistory.undo(); } function redo() { productEditModel.value.editHistory.redo(); } </script> <template> <div v-for="productImage of productEditModel.images" :key="productImage.key"> <ImageItem :imageModel="productImage" @changeImageDescription="handleChangeImageDescription"></ImageItem> </div> <button @click="undo">Undo</button> <button @click="redo">Redo</button> <button @click="addImage">Add image</button> </template>
ProductImage.js
export class ProductImage { constructor() { this.key = Math.random(); this.description = ''; } }
ImageItem.vue
<script setup> import { defineProps, defineEmits } from 'vue'; const emit = defineEmits(['removeImage', 'changeImage', 'changeImageDescription']); const props = defineProps({ imageModel: { type: Object, required: true } }); function handleDescriptionChange(description) { emit('changeImageDescription', props.imageModel, description); } </script> <template> <div class="image-item"> <img src="" alt="Example image here..." /> Description: <textarea :value="props.imageModel.description" rows="4" @change="(event) => handleDescriptionChange(event.target.value)" /> </div> </template> <style> .image-item { border: 1px solid #aaa; padding: 10px; } .image-item * { vertical-align: middle; } img { min-width: 100px; min-height: 100px; border: 1px solid #d0d0d0; } </style>
EditHistory.js
export class EditHistory { constructor() { this.undoStack = []; this.redoStack = []; } do(action) { action.execute(); this.undoStack.push(action); this.redoStack = []; } undo(count = 1) { count = Math.min(this.undoStack.length, count); for (let i = count; i > 0; i--) { const undoAction = this.undoStack.pop(); undoAction.unExecute(); this.redoStack.push(undoAction); } } redo(count = 1) { count = Math.min(this.redoStack.length, count); for (let i = count; i > 0; i--) { const redoAction = this.redoStack.pop(); redoAction.execute(); this.undoStack.push(redoAction); } } canUndo() { return this.undoStack.length > 0; } canRedo() { return this.redoStack.length > 0; } }
ProductEditModel.js
import { AddImageAction } from "./AddImageAction"; import { ChangeImageDescriptionAction } from "./ChangeImageDescriptionAction"; import { EditHistory } from "./EditHistory"; export class ProductEditModel { constructor() { this.images = []; // Array of ProductImage instances. this.editHistory = new EditHistory(); } addImage() { this.editHistory.do(new AddImageAction(this)); } changeImageDescription(imageModel, description) { this.editHistory.do(new ChangeImageDescriptionAction(this, imageModel, description)); } }
AddImageAction.js
import { ProductImage } from './ProductImage.js'; export class AddImageAction { constructor(productEditModel) { this.productEditModel = productEditModel; this.addedImageModel = null; } getDescription() { return "Afbeelding toevoegen"; } execute() { this.addedImageModel = new ProductImage(); this.productEditModel.images.push(this.addedImageModel); } unExecute() { const index = this.productEditModel.images.indexOf(this.addedImageModel); console.assert(index > -1); this.productEditModel.images.splice(index, 1); } }
ChangeImageDescriptionAction.js
export class ChangeImageDescriptionAction { constructor(productEditModel, productImageEditModel, description) { this.productEditModel = productEditModel; this.productImageEditModel = productImageEditModel; this.description = description; this.originalDescription = this.productImageEditModel.description; } getDescription() { return "Afbeelding omschrijving veranderen"; } execute() { this.productImageEditModel.description = this.description; console.log("Changed description to: " + this.description); } unExecute() { this.productImageEditModel.description = this.originalDescription; console.log("Changed description back to original: " + this.originalDescription); } }
内容的提问来源于stack exchange,提问作者user2190492
相关产品推荐
相关产品推荐

