Vue3中如何在gridstack.js里移除单个组件?
问题解决:gridstack.js + Vue 移除组件失败及报错处理
我尝试在gridstack.js中通过索引(index)和键(key)移除对象中的单个组件,组件由v-for指令创建。我已获取到index和key,但按照文档使用components.splice(index,1)和grid.removeWidget(key, false)方法无法实现移除,还出现了'Uncaught TypeError: Cannot read properties of undefined (reading 'splice')'错误,请问我哪里操作有误?
用户提供的原始代码:
<template> <div class="right"> <button class="ds" @click="addNewWidget()">Add Widget</button> //works okay <button class="ds" @click="deleteWidget()">Clear</button> </div> <section class="grid-stack"> <div v-for="(component, key, index) in components" :key="'component' + index" :gs-id="key" class="grid-stack-item" :gs-x="component.gridPos.x" :gs-y="component.gridPos.y" :gs-h="component.gridPos.h" :gs-w="component.gridPos.w" gs-auto-position="true"> <div class="grid-stack-item-content"> <component :is="component.name" v-bind="component.props" /> //Uncaught TypeError: Cannot read properties of undefined (reading 'splice') <button @click="remove(index, key, component)">remove</button> </div> </div> </section> </template> <script> import { ref, onMounted, reactive, nextTick } from 'vue'; import "gridstack/dist/h5/gridstack-dd-native"; import 'gridstack/dist/gridstack.min.css'; import { GridStack } from 'gridstack'; import BarChart from './BarChart.vue'; import PieChart from './PieChart.vue'; import LineChart from './LineChart.vue'; import Widget from './HalfDoughnut.vue'; export default { name: "Dashboard", setup() { let info = ref(""); let grid = null; let components = reactive({ yourRandomComponent1: { name: "BarChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, yourRandomComponent2: { name: "PieChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, }); onMounted(() => { grid = GridStack.init({ float: true, cellHeight: "70px", minRow: 1, }); grid.on("dragstop", (event, element) => { console.log("move event!", event, element); const node = element.gridstackNode; info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`; }); }); // this will of course only work once because of the object-key function addNewWidget() { components.yourRandomComponent3 = { name: "Widget", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }; // we have to wait for vue to update v-for, // until then the querySelector wont find the element nextTick(() => { console.log(grid); let compEl = document.querySelector('[gs-id="yourRandomComponent3"]'); console.log(compEl); grid.makeWidget(compEl); }); console.warn("i will only work once, fix my inputs to reuse me"); } function remove(index, key) { console.log(key) //Uncaught TypeError: Cannot read properties of undefined (reading 'splice') components.splice(index, 1); grid.removeWidget(key, false); } function deleteWidget(){ grid.removeAll(true); } return { info, components, addNewWidget, deleteWidget, remove }; }, components: { // eslint-disable-next-line vue/no-unused-components BarChart, PieChart, LineChart, Widget } } </script>
错误原因分析
- components是对象而非数组:你定义的
components是reactive({})对象,而splice是数组专属方法,对象没有该方法,这是报错的直接原因。 - grid.removeWidget参数错误:该方法需要传入DOM元素、gridstack节点对象,或DOM元素的
id属性值,直接传对象的key字符串无法被识别。 - v-for的key绑定不合理:用
'component' + index作为key,列表变化时可能触发Vue的DOM复用错误,应该使用唯一稳定的标识。
修正方案
1. 将components改为数组结构(推荐)
数组更适配列表的增删操作,配合v-for逻辑更直观:
let components = reactive([ { key: "yourRandomComponent1", name: "BarChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, { key: "yourRandomComponent2", name: "PieChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, ]);
2. 修改remove函数
function remove(index, key) { // 从数组中移除组件 components.splice(index, 1); // 等待Vue更新DOM后,再操作gridstack移除对应组件 nextTick(() => { const widgetEl = document.querySelector(`[gs-id="${key}"]`); if (widgetEl) { grid.removeWidget(widgetEl, false); } }); }
3. 修正v-for绑定
<div v-for="(component, index) in components" :key="component.key" :gs-id="component.key" class="grid-stack-item" :gs-x="component.gridPos.x" :gs-y="component.gridPos.y" :gs-h="component.gridPos.h" :gs-w="component.gridPos.w" gs-auto-position="true">
将key绑定为component.key保证唯一性,同时调整v-for遍历逻辑适配数组结构。
4. 调整addNewWidget函数(适配数组)
function addNewWidget() { const newKey = `yourRandomComponent${components.length + 1}`; components.push({ key: newKey, name: "Widget", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }); nextTick(() => { let compEl = document.querySelector(`[gs-id="${newKey}"]`); if (compEl) { grid.makeWidget(compEl); } }); }
完整修正代码
<template> <div class="right"> <button class="ds" @click="addNewWidget()">Add Widget</button> <button class="ds" @click="deleteWidget()">Clear</button> </div> <section class="grid-stack"> <div v-for="(component, index) in components" :key="component.key" :gs-id="component.key" class="grid-stack-item" :gs-x="component.gridPos.x" :gs-y="component.gridPos.y" :gs-h="component.gridPos.h" :gs-w="component.gridPos.w" gs-auto-position="true"> <div class="grid-stack-item-content"> <component :is="component.name" v-bind="component.props" /> <button @click="remove(index, component.key)">remove</button> </div> </div> </section> </template> <script> import { ref, onMounted, reactive, nextTick } from 'vue'; import "gridstack/dist/h5/gridstack-dd-native"; import 'gridstack/dist/gridstack.min.css'; import { GridStack } from 'gridstack'; import BarChart from './BarChart.vue'; import PieChart from './PieChart.vue'; import LineChart from './LineChart.vue'; import Widget from './HalfDoughnut.vue'; export default { name: "Dashboard", setup() { let info = ref(""); let grid = null; // 改为数组结构 let components = reactive([ { key: "yourRandomComponent1", name: "BarChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, { key: "yourRandomComponent2", name: "PieChart", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }, ]); onMounted(() => { grid = GridStack.init({ float: true, cellHeight: "70px", minRow: 1, }); grid.on("dragstop", (event, element) => { console.log("move event!", event, element); const node = element.gridstackNode; info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`; }); }); function addNewWidget() { const newKey = `yourRandomComponent${components.length + 1}`; components.push({ key: newKey, name: "Widget", props: {}, gridPos: { x: 0, y: 1, w: 6, h: 7 } }); nextTick(() => { let compEl = document.querySelector(`[gs-id="${newKey}"]`); if (compEl) { grid.makeWidget(compEl); } }); } function remove(index, key) { // 移除数组中的元素 components.splice(index, 1); // 等待DOM更新后移除gridstack组件 nextTick(() => { const widgetEl = document.querySelector(`[gs-id="${key}"]`); if (widgetEl) { grid.removeWidget(widgetEl, false); } }); } function deleteWidget(){ grid.removeAll(true); // 同步清空数组 components.length = 0; } return { info, components, addNewWidget, deleteWidget, remove }; }, components: { BarChart, PieChart, LineChart, Widget } } </script>
内容的提问来源于stack exchange,提问作者sd23
相关产品推荐
相关产品推荐

