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

Vue3集成Gridstack.js时网格项内嵌Vue组件响应式失效问题

问题根源

响应式失效的核心原因非常明确:你传入addWidget的content是div.outerHTML——这只是一份静态HTML字符串快照,和你之前执行app.mount(div)生成的响应式Vue DOM实例没有任何关联。相当于你先把Vue组件渲染到了一个不在页面DOM树里的临时div上,转头只复制了这个div的静态HTML代码塞给Gridstack,真正带响应式能力的DOM节点根本没被插到页面里。后续你再调用app.mount(div.id)的时候,Gridstack还没把对应id的节点渲染到页面上,挂载操作完全不生效,最终页面上显示的只是没有任何响应式绑定的静态HTML,自然不会更新。

实现方案

核心思路是放弃给Gridstack传静态HTML字符串的逻辑,等Gridstack创建好真实的页面DOM节点后,直接把Vue组件挂载到节点内置的.grid-stack-item-content容器上,就能完整保留Vue的响应式能力。具体实现要点:

  • 初始化Gridstack时不提前传入内容,避免静态HTML覆盖后续挂载的Vue节点
  • 新增/初始化网格项时,先让Gridstack生成真实DOM,再执行Vue挂载操作
  • 统一维护所有创建的Vue应用实例,在网格项删除、页面卸载时主动执行卸载逻辑,避免内存泄漏
  • 用Vue的h()渲染函数挂载组件、传递props,比extends扩展组件的方式更稳定,不会破坏组件原有的生命周期和响应式逻辑

修正后的完整代码

<template>
    <div class="p-6 h-full flex flex-col">
        <header class="flex flex-row items-center justify-between mb-6">
            <div>
                <h1 class="text-3xl font-bold">
                    Workbench
                </h1>
                <p class="leading-6 text-gray-600 text-sm mt-2">
                    {{ info }}
                </p>
            </div>
            <div class="flex flex-row items-center">
                <button type="button" @click="addPanel()">Add Panel</button>
            </div>
        </header>

        <div class="flex-1">
            <section class="grid-stack"></section>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, createApp, h } from "vue"
import TestPanel from "./../components/panels/TestPanel.vue"

let grid = null;
// 存储网格节点和对应Vue实例的映射关系,用于清理
const appInstances = new Map()

const items = [
    { x: 0, y: 0, h: 4, w: 6, comp: TestPanel, props: { title: "面板1" } },
    { x: 7, y: 0, h: 4, w: 6, comp: TestPanel, props: { title: "面板2" } },
    { x: 0, y: 5, h: 4, w: 4, comp: TestPanel, props: { title: "面板3" } },
    { x: 4, y: 5, h: 4, w: 4, comp: TestPanel, props: { title: "面板4" } },
    { x: 8, y: 5, h: 4, w: 4, comp: TestPanel, props: { title: "面板5" } },
];

/**
 * 把Vue组件挂载到指定Gridstack网格项上
 * @param {HTMLElement} widgetEl Gridstack生成的网格项节点
 * @param {Component} component 要挂载的Vue组件
 * @param {Object} props 传给组件的属性
 */
function mountVueToWidget(widgetEl, component, props = {}) {
    const contentContainer = widgetEl.querySelector('.grid-stack-item-content')
    contentContainer.innerHTML = ''
    const app = createApp({
        render: () => h(component, props)
    })
    app.mount(contentContainer)
    appInstances.set(widgetEl, app)
}

onMounted(() => {
    grid = GridStack.init({
        cellHeight: "70px",
        minRow: 1,
    });

    // 监听网格项删除事件,自动卸载对应Vue实例
    grid.on('removed', (_, removedItems) => {
        removedItems.forEach(item => {
            const app = appInstances.get(item.el)
            if (app) {
                app.unmount()
                appInstances.delete(item.el)
            }
        })
    })

    // 初始化加载默认网格项
    items.forEach(itemConfig => {
        const { comp, props, ...gridOption } = itemConfig
        const widget = grid.addWidget(gridOption)
        mountVueToWidget(widget, comp, props)
    })
});

onBeforeUnmount(() => {
    // 页面销毁时清理所有实例和Gridstack
    appInstances.forEach(app => app.unmount())
    appInstances.clear()
    grid.destroy(false)
})

function addPanel() {
    // 先生成空的网格项
    const widget = grid.addWidget({
        x: 0,
        y: 0,
        w: 6,
        h: 3,
    })
    // 挂载Vue组件,直接传ref/reactive类型的props即可保持响应式
    mountVueToWidget(widget, TestPanel, { test: "this is a test" })
}
</script>

<style>
    .grid-stack-item-content {
        background-color: #18BC9C;
        overflow: hidden; /* 避免组件内容溢出网格项边界 */
    }
</style>

优化提示

  • 如果需要实现网格项缩放后的组件自适应,可以监听Gridstack的resizestop事件,把最新的宽高值通过props传给对应组件,组件内部监听尺寸变化做响应式适配即可
  • 传递props时直接传入ref、reactive包裹的响应式变量,Vue会自动保持响应式绑定,不需要额外处理
  • 如果需要在网格项里支持Vue的全局插件、全局属性,可以把createApp的逻辑抽成公共方法,统一注册插件后再挂载,和普通Vue应用的使用逻辑完全一致

内容的提问来源于stack exchange,提问作者Nick Verheijen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:18:23