Vue动态添加DOM后二次迭代click事件失效问题求助
动态生成元素的Vue点击事件失效问题解决
问题描述
实现树形结构交互时,点击按钮通过jQuery向DOM注入HTML字符串添加输入框和按钮,但二次生成的按钮点击事件完全无法触发。
原始HTML代码
<div class="text-start parent"> <div class="bg-info add"> <div class="pt-4 ps-4"> <input type="text" class="d-inline"> </div> <div class="pb-2 child"></div> <div style="margin-right: 60px"> <button @click="add($event)" class="btn btn-danger" style="width: 35px; height: 35px; margin-top: -25px;"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute"></span> </div> </div> <div> <div style="margin-right: 20px"> <button @click="secAdd($event)" class="btn btn-primary" style="width: 35px; height: 35px; margin-bottom: -20px"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute; margin-top: 23px;"></span> </div> </div> </div>
原始Vue代码
data() { return { count: 1, component: `<div class="bg-info add"> <div class="pt-4 ps-4"> <span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> </svg></span> <input type="text" class="d-inline"> <span style="width: 25px; height: 15px; border-bottom: 1px solid black; position: absolute;"></span> </div> <div class="pb-2 child" style="margin-right: 40px; border-right: 1px solid rgba(0,0,0,0.45);min-height: 40px"></div> <div style="margin-right: 60px"> <button @click="add($event)" class="btn btn-danger" style="width: 35px; height: 35px; margin-top: -25px;"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute"></span> </div> </div>`, } }, methods: { add(event) { $(event.target).closest('.add').children('.child').append(this.component) }, secAdd(event) { $(event.target).closest('.parent').children('.add').append(this.component) }, }
问题根源
直接通过jQuery将HTML字符串插入DOM时,Vue不会解析这些字符串中的@click指令。Vue仅在初始化阶段解析模板中的指令并绑定事件,动态插入的纯HTML对Vue而言只是普通文本,不会进行任何指令处理或事件绑定。
解决方案
方案1:Vue数据驱动(推荐,符合Vue设计思想)
放弃直接操作DOM,改用数组维护树形结构数据,通过组件递归渲染实现动态节点添加。
步骤1:封装树形节点子组件 TreeItem.vue
<template> <div class="bg-info add"> <div class="pt-4 ps-4"> <span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> </svg> </span> <input type="text" class="d-inline"> <span style="width: 25px; height: 15px; border-bottom: 1px solid black; position: absolute;"></span> </div> <div class="pb-2 child" style="margin-right: 40px; border-right: 1px solid rgba(0,0,0,0.45);min-height: 40px"> <!-- 递归渲染子节点 --> <TreeItem v-for="(node, index) in nodeChildren" :key="index" :nodeChildren="node.children" @add-child="handleAddChild(index)" /> </div> <div style="margin-right: 60px"> <button @click="handleAddSelf" class="btn btn-danger" style="width: 35px; height: 35px; margin-top: -25px;"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute;"></span> </div> </div> </template> <script> export default { name: 'TreeItem', props: { nodeChildren: { type: Array, default: () => [] } }, methods: { handleAddSelf() { // 向父组件发送添加当前节点子节点的事件 this.$emit('add-child', { type: 'self' }) }, handleAddChild(index) { // 向父组件发送添加指定子节点的子节点事件 this.$emit('add-child', { type: 'child', targetIndex: index }) } } } </script>
步骤2:父组件使用子组件并维护树形数据
<template> <div class="text-start parent"> <TreeItem :nodeChildren="rootNodes" @add-child="handleAddNode" /> <div> <div style="margin-right: 20px"> <button @click="handleRootAdd" class="btn btn-primary" style="width: 35px; height: 35px; margin-bottom: -20px;"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute; margin-top: 23px;"></span> </div> </div> </div> </template> <script> import TreeItem from './TreeItem.vue' export default { components: { TreeItem }, data() { return { rootNodes: [] // 根节点数组,每个元素包含children子节点数组 } }, methods: { handleAddNode(payload) { if (payload.type === 'self') { // 给当前节点添加子节点 this.rootNodes.push({ children: [] }) } else if (payload.type === 'child') { // 给指定子节点添加子节点 this.rootNodes[payload.targetIndex].children.push({ children: [] }) } }, handleRootAdd() { // 根节点新增节点 this.rootNodes.push({ children: [] }) } } } </script>
方案2:事件委托(不推荐,仅兼容现有jQuery场景)
如果必须保留jQuery操作DOM的逻辑,可通过事件委托将事件绑定到父元素,通过目标元素的类名判断触发对应方法:
修改Vue代码,添加事件绑定与解绑:
data() { // 原有data内容不变,移除component字符串中的@click="add($event)" return { count: 1, component: `<div class="bg-info add"> <div class="pt-4 ps-4"> <span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> </svg></span> <input type="text" class="d-inline"> <span style="width: 25px; height: 15px; border-bottom: 1px solid black; position: absolute;"></span> </div> <div class="pb-2 child" style="margin-right: 40px; border-right: 1px solid rgba(0,0,0,0.45);min-height: 40px"></div> <div style="margin-right: 60px"> <button class="btn btn-danger" style="width: 35px; height: 35px; margin-top: -25px;"></button> <span style="width: 20px; height: 1px; border-bottom: 1px solid black; position: absolute;"></span> </div> </div>`, } }, methods: { add(event) { $(event.target).closest('.add').children('.child').append(this.component) }, secAdd(event) { $(event.target).closest('.parent').children('.add').append(this.component) }, }, mounted() { // 绑定事件委托到.parent元素 $('.parent').on('click', '.btn-danger', (event) => this.add(event)) $('.parent').on('click', '.btn-primary', (event) => this.secAdd(event)) }, beforeUnmount() { // 组件销毁前解绑事件,避免内存泄漏 $('.parent').off('click', '.btn-danger') $('.parent').off('click', '.btn-primary') }
总结
Vue的核心是数据驱动视图,直接操作DOM会绕过Vue的响应式系统,导致事件绑定、数据同步等问题。优先使用方案1,通过数组维护数据+组件递归渲染,这是Vue开发的标准范式;方案2仅作为临时兼容方案,不建议长期使用。
内容的提问来源于stack exchange,提问作者Eamd
相关产品推荐
相关产品推荐

