Vue 3: 如何从父组件向子组件发送事件?

15

现在我需要从父组件向子组件发出事件。 我看到在Vue版本3中,$on$off$once实例方法被删除了。 应用程序实例不再实现事件发射器接口。

那么我现在如何在Vue版本3中从父组件发出事件并在子组件中监听呢?


你能不能不使用props?从父组件传递下来的响应式属性作为prop自动允许你在子组件上进行操作。 - Ali Gajani
@AliGajani 我说的是关于Vue 3中类似这样的操作 https://v3.vuejs.org/guide/migration/events-api.html - Andreas Hunter
通过props进行父子组件通信是一种策略,我认为在Vue 2和Vue 3之间在这方面没有任何变化。如果您想从父组件向子组件发出事件,则props是正确的方法。由于通过父组件传递到prop中的属性将是data()或computed(),因此它将是响应式的,允许您在子组件上观察并执行操作。这只是我的个人看法。 - Ali Gajani
你是如何在Vue 2中使用那些方法将事件从父组件传递到子组件的?它们似乎是在全局可访问的“总线”上触发事件,因此不是严格的父->子通信。你可以使用任何全局事件发射器来实现这个目的吗? - Xinchao
5个回答

16
您可以使用“Refs”访问子方法。 https://v3.vuejs.org/guide/composition-api-template-refs.html
<!-- Parent -->
<template>
    <ChildComponent ref="childComponentRef" />
</template>

<script>
import { ref } from 'vue'
import ChildComponent from './components/ChildComponent.vue'

export default { 
  setup( ) {
    const childComponentRef = ref()
    childComponentRef.value.expandAll();

    return { childComponentRef }
  }
}
</script>

<!-- Child -->
<script>
export default {
    setup() {
        const expandAll= () => {
            //logic...
        }

        return { expandAll}
    }
}
</script>

7
如果使用 <script setup>,请参考组件上的 refs。您必须使用 defineExpose({}) 暴露您希望从父级调用的函数。 - Emptybox

6

在父子组件之间,不应该从子组件监听父组件的事件,而是应该向子组件传递一个prop,如果子组件需要更新数据,则应该从子组件向父组件发出事件以更新状态。

只读生命周期: 父组件 > Prop > 子组件

读/写生命周期: 父组件 > Prop > 子组件 > 发送事件 > 父组件 > 更新 > 子组件更新


6
例如,“expandAll” 怎么样?我有一个父组件,其下有一个带可展开行的数据表格子组件。每个子行又包含了一个带可展开行的孙子组件。我想点击按钮展开或折叠所有子和孙子组件的所有数据行。如果我只是在属性中传递一个标志,它会保持行的展开/折叠状态,并且不允许用户更改任何单个行的状态...? - Predrag Stojadinović
那么关于一个全局处理程序来捕获 ctrl+v 呢?当页面上有上传组件 ,我想将剪贴板的内容传送出去。 - Dennis de Laat
如果我想触发子元素内部的一个函数怎么办?不是像“在属性为false之前一直这样做”,而是“只做一次,如果再次触发则再次执行”。 - Fred

2

您可以使用作用域插槽来实现这一点。

例如,在父组件中使用“插槽”:

Parent.vue:

<script setup lang="ts">
import { useToggle } from '@/composables'

const props = defineProps({
  show: {
    type: Boolean,
    required: true
  }
})

const { isOpen, close, open, toggle } = useToggle(props.show)
</script>

<template>
  <slot
    :on-open="open"
    :on-close="close"
    :on-toggle="toggle"
  />
</template>

*我们使用了基本的切换组合。

Grandparent.vue组件中,我们可以将一些方法触发事件限定于父级的Child.vue组件范围内,如下所示:

GrandParent.vue:

<template>
  <Parent>
    <template #default="{ onToggle }">
      <Child 
        @toggle="onToggle" 
      />
    </template>
  </Parent>
</template>

*将#default更改为您的插槽名称,如果没有名称,则保留为默认值。

然后,在Child.vue组件中,我们确保向上传递事件:

Child.vue:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'toggle'): void
}>()

const toggle = () => {
  emit('toggle')
}
</script>

<template>
  <button @click="toggle">Toggle Event</button>
</template>

1

我想到了一种从父组件向子组件发出事件的方法。请注意,这是一种非常丑陋的解决方法!!

//child

setup(){
  // get the current instance
  const instance = getCurrentInstance();

  // function to find the parent instance
  const getParentComponent = (name: string) => {
    let component = null;
    if (instance) {
      let parent = instance.parent;
      while (parent && !component) {
        if (parent.type.name === name) {
          component = parent;
        }
        parent = parent.parent;
      }
      return component;
    } else {
      return null;
    }
  };

  // listener that will be called from within the parent component
  const eventFunction = () => {
    console.log('event fired !!')
  }
      
  onMounted(() => {
    const $parent = getParentComponent('ParentComponentName');

    // register the current instance to the parent component
    if($parent && $parent.props && $parent.props.childInstances){
      ($parent.props.childInstances as any[]).push(instance)
    }
  })

  return {
    eventFunction
  }
}

//parent

name: 'ParentComponentName',
props: {
  childInstances: {
    type: Array as PropType<any[]>,
    required: false, 
    default: () => [] as any[]
  }
},
setup(props){
  const emitChildComponentEvent = (event: string) => {
    if(props.childInstances.length > 0){
      props.childInstances.forEach((component: any) => {
        if(typeof component.proxy[event] === 'function'){
          component.proxy[event]()
        }
      })
    }
  }

  onMounted(() => {
    emitChildComponentEvent('eventFunction');
  })
}

1

如果你像我一样,在Vue2中从任何父/子到任何子/父上调用一些事件时,使用this.$root.$on(...)this.$root.$emit(...)来保持代码更加清晰,而不是分别使用大量的emits和props并使代码爆炸..

从Vue3文档中,事件总线模式可以通过使用实现事件发射器接口的外部库来替换。使用实现pub-sub模式的库或编写它。 vue3 event description

现在,如果您正在使用Option-API(例如vue 2),则需要导入该事件文件,然后在任何组件中立即使用它。

如果您使用的是<script setup>,则需要添加额外的步骤才能使用该事件库,这是代码。

这是发布-订阅JavaScript模式的基本示例,请不要忘记添加off方法并在beforeUnmounted(v3),beforeDestroy(v2)上调用它,以避免为每个已安装的调用执行多个函数)。

    //event.js
    
class Event{
        constructor(){
            this.events = {};
        }
    
        on(eventName, fn) {
            this.events[eventName] = this.events[eventName] || [];
            this.events[eventName].push(fn);
        }
        emit = (eventName, data)=> (this.events[eventName]) ? this.events[eventName].forEach( fn => fn(data)) : '' ;  
    }
    
    export default new Event();

如果你正在使用类似Vue2的Option-API语法: //在Vue组件中

import event from './event';
//mounted or any methods
even.on('GG', data=> console.log(`GG Event received ${data}`))

//显然,你需要从另一个组件中发出它 //...

import event from './event';
//on mounted or methods click or...
even.emit('GG', {msg:"Vue3 is super Cool"});

如果你正在使用 <script setup>,这意味着所有变量和方法默认都会暴露给模板。

//in main.js
import event from './event.js';
//..
app.config.globalProperties.$event = event;
//..

//note if you have an error make sure that you split the the app chaining, like this :

let app = createApp(App);
app.config.globalProperties.$event = event;
app.mount('#app');

//添加一个名为 useEvent.js 的文件

// useEvent.js
import { getCurrentInstance } from 'vue'
export default  useEvent => getCurrentInstance().appContext.app.config.globalProperties.$event;

//在 <script setup> 中使用它

import useEvent from '@/useEvent'
const event    =  useEvent();
event.emit('GG');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接