You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Vue动态组件如何动态捕获子组件专属事件并避免方法频繁触发?

Vue动态组件如何动态捕获子组件专属事件并避免方法频繁触发?

兄弟,我太懂你踩的这个坑了!之前用动态组件绑定事件时,我也犯过直接在v-on里调用函数返回对象的错,结果那个函数疯狂执行,控制台看调用次数都麻了😂。我来给你捋捋问题出在哪,以及怎么彻底解决~

先搞懂为什么函数会“失控调用”

你现在写的v-on="{ ...capturedEvent(componentData) }",本质上是Vue每次渲染组件时,都会执行一遍capturedEvent(componentData)来生成事件绑定对象。只要父组件有任何响应式数据更新(哪怕和这个动态组件八竿子打不着),这个函数就会被调用一次,所以才会出现“每秒都在调用”的失控情况——这完全是渲染机制导致的,不是你的函数逻辑有问题。

最优解决方案:用计算属性缓存事件绑定对象

我们的核心目标是:只有当组件类型变化或者componentData更新时,才重新生成事件绑定对象,其他时候直接用缓存的结果。这时候Vue的computed计算属性就派上大用场了,它天生自带缓存,只有依赖的响应式数据变化时才会重新计算。

具体步骤如下:

1. 提前定义组件与事件的映射关系

先把每个组件对应的事件名和处理逻辑整理成一个映射表,这样不用每次都临时判断:

// 在你的组件选项里定义
data() {
  return {
    componentEventMap: {
      GeButton: {
        'button-click': (data) => this.buttonClick(data)
      },
      SugerenciaGE: {
        'sugerencia-clickada': (data) => this.buttonClick(data)
      }
    }
  }
},
methods: {
  buttonClick(data) {
    // 你的统一事件处理逻辑
    console.log('捕获到事件,数据:', data)
  }
}

2. 用计算属性生成动态事件绑定对象

把生成事件对象的逻辑放到计算属性里,让Vue帮我们缓存结果:

computed: {
  dynamicComponentEvents() {
    const currentComp = this.componentToRender.component
    // 如果当前没有要渲染的组件,或者映射表中没有对应项,返回空对象
    if (!currentComp || !this.componentEventMap[currentComp]) {
      return {}
    }

    // 从映射表中拿到当前组件的事件配置,包装成带最新componentData的处理函数
    const eventConfig = this.componentEventMap[currentComp]
    return Object.fromEntries(
      Object.entries(eventConfig).map(([eventName, handler]) => [
        eventName,
        // 这里用箭头函数包装,确保触发事件时能拿到最新的componentData
        () => handler(this.componentData)
      ])
    )
  }
}

3. 模板中直接绑定计算属性

现在模板里直接用这个计算属性,再也不会疯狂调用函数了:

<component
  :is="componentToRender.component"
  v-if="componentToRender.component"
  v-bind="{ data: componentData }"
  v-on="dynamicComponentEvents"
/>

备选方案:用watch维护响应式事件对象

如果你不想用计算属性,也可以通过watch监听组件类型和componentData的变化,手动维护一个响应式的事件对象:

data() {
  return {
    currentEvents: {},
    componentEventMap: {
      GeButton: 'button-click',
      SugerenciaGE: 'sugerencia-clickada'
    }
  }
},
watch: {
  // 监听组件类型变化
  'componentToRender.component': {
    immediate: true,
    handler() {
      this.updateDynamicEvents()
    }
  },
  // 监听componentData变化(deep: true是为了监听对象内部属性更新)
  componentData: {
    deep: true,
    handler() {
      this.updateDynamicEvents()
    }
  }
},
methods: {
  updateDynamicEvents() {
    const currentComp = this.componentToRender.component
    if (!currentComp) {
      this.currentEvents = {}
      return
    }

    const eventName = this.componentEventMap[currentComp]
    this.currentEvents = {
      [eventName]: () => this.buttonClick(this.componentData)
    }
  },
  buttonClick(data) {
    // 统一处理逻辑
  }
}

模板里直接绑定这个currentEvents即可:

<component
  :is="componentToRender.component"
  v-if="componentToRender.component"
  v-bind="{ data: componentData }"
  v-on="currentEvents"
/>

最后再划个重点

千万不要在v-bindv-on的表达式里直接调用函数返回对象(比如v-on="{ ...fn() }"),这种写法会让函数在每次组件渲染时都执行一遍,性能拉胯不说,还容易出现你遇到的“失控调用”问题。用计算属性或者watch维护的响应式对象才是正确姿势~

备注:内容来源于stack exchange,提问作者tsiPlus

火山引擎 最新活动