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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 01:25:29