You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 15:55:18