likes
comments
collection
share

详细说说 Vue 组件之间的通信方式

作者站长头像
站长
· 阅读数 28

在 Vue.js 中,组件间的通信是一个很重要的话题。

我们可以分为几大类进行总结:

父子组件的通信

props 和 $emit

// 父组件
<template>
  <child :parentMsg="msg" @childEvent="handleChildEvent"></child>
</template>

<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  data() {
    return {
      msg: 'Hello from parent'
    }
  },
  methods: {
    handleChildEvent(payload) {
      console.log('Received event from child with payload:', payload);
    }
  }
}
</script>

// 子组件
<template>
  <div>
    <p>{{ parentMsg }}</p>
    <button @click="notifyParent">Notify Parent</button>
  </div>
</template>

<script>
export default {
  props: ['parentMsg'],
  methods: {
    notifyParent() {
      this.$emit('childEvent', 'Hello from child');
    }
  }
}
</script>

非父子组件之间的通信

可以通过 EventBus(事件总线)进行。创建一个全新的 Vue 实例作为中央事件总线,用它来触发事件和监听事件。

// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();

// Component A
<template>
  <button @click="emitEvent">Emit an Event</button>
</template>

<script>
import { EventBus } from './eventBus.js';

export default {
  methods: {
    emitEvent() {
      EventBus.$emit('event', 'Hello from Component A');
    }
  }
}
</script>

// Component B
<template>
  <p>{{ message }}</p>
</template>

<script>
import { EventBus } from './eventBus.js';

export default {
  data() {
    return {
      message: ''
    }
  },
  created() {
    EventBus.$on('event', (payload) => {
      this.message = payload;
    });
  }
}
</script>

Vuex

当项目较大或者多个组件需要共享状态时,可以使用 Vuex。Vuex 将状态集中管理,提供统一的接口进行状态的读取或修改。

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

// Component A
<template>
  <button @click="increment">Increment</button>
</template>

<script>
import { mapMutations } from 'vuex';

export default {
  methods: {
    ...mapMutations([
      'increment' // maps this.increment() to this.$store.commit('increment')
    ])
  }
}
</script>

// Component B
<template>
  <p>{{ count }}</p>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState([
      'count' // maps this.count to this.$store.state.count
    ])
 

其他的通信方式

$refs

可以在模板中为子组件或 DOM 元素添加 ref 属性,通过 $refs 属性来访问子组件的实例,进而调用子组件的方法或者访问子组件的数据。

// ChildComponent.vue
export default {
  name: "ChildComponent",
  methods: {
    childMethod() {
      console.log('Hello from ChildComponent!');
    }
  }
}


// ParentComponent.vue
export default {
  name: "ParentComponent",
  methods: {
    callChildMethod() {
      this.$refs.child.childMethod();
    }
  }
}

在上面的代码中,this.$refs.child 就是 ChildComponent 的实例。所以我们可以直接在其后面调用 childMethod 方法。

需要注意的是,$refs 只会在组件渲染完成之后被填充,并且它们不是响应式的。这意味着你不能在模板中使用 $refs,并且你也不能在 $refs 上使用 Vue 的响应式系统。

如果你需要在模板中访问子组件的数据或者方法,你可能需要考虑使用 props 或者自定义事件。

$refs 并不是响应式的。这意味着如果 $refs 的内容发生变化,Vue 不会自动检测到这种变化,并且这种变化也不会触发视图的更新。

<template>
  <div>{{ $refs.myComponent.someData }}</div>
</template>

$refs 不是响应式的,所以如果 someData 改变了,这个 <div> 的内容并不会更新。

同样的,你也不能在计算属性或者侦听器中使用 $refs,因为它们依赖于 Vue 的响应式系统。

所以,你应该只在方法中使用 $refs,例如在某个点击事件的处理函数中,或者在 mounted 生命周期钩子中。在这些地方,你可以确保组件已经被渲染了,所以 $refs 已经被正确地填充。

$children$parent

<template>
  <div>
    <child-component></child-component>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log(this.$children);  // 输出包含 ChildComponent 实例的数组
    console.log(this.$parent);  // 输出 ParentComponent 实例
  }
}
</script>

注意,尽管有父子组件关系,但是并不推荐在应用程序逻辑中使用 $children$parent,因为它们使得组件的耦合性增强,降低了组件的可重用性。

provide / inject

这是一个提供数据给任意深度子组件使用的方式,主要服务于开发高阶插件/组件库。一个组件可以通过 provide 选项来提供数据,然后在任何子组件中通过 inject 选项来接收数据。

<template>
  <div>
    <child-component></child-component>
  </div>
</template>

<script>
export default {
  provide: {
    foo: 'bar'
  }
}
</script>

<!-- 在子组件中 -->
<script>
export default {
  inject: ['foo'],
  mounted() {
    console.log(this.foo);  // 输出 'bar'
  }
}
</script>

$attrs$listeners

当你使用 $attrs$listeners 的时候,你其实就是在说:“我不关心这些属性或者事件是什么,我只知道我需要把他们传递下去。”

这让你的组件更加的灵活,因为它可以接受任意的属性和事件,而不需要明确的声明他们。

假设你是一个派对的主办者,你需要发送邀请给大家。

你有一堆的属性(也就是邀请函的内容),你需要将它们发送给你的朋友(也就是子组件)。我们可以将这些属性想象成是你的名字、派对的地点、时间等等。

在 Vue 中,你可以将这些属性传递给子组件,就像这样:

<template>
  <Party
    :name="myName"
    :location="partyLocation"
    :time="partyTime"
    @rsvp="handleRSVP"
  />
</template>

<script>
import Party from './Party.vue';

export default {
  components: {
    Party
  },
  data() {
    return {
      myName: 'John',
      partyLocation: 'My House',
      partyTime: '8:00pm'
    };
  },
  methods: {
    handleRSVP(response) {
      console.log(`Got RSVP response: ${response}`);
    }
  }
}
</script>

但是,你的朋友可能并不关心所有的属性,他可能只关心名字和派对的地点。

而且,他可能还需要处理其他的属性,这些属性并不在你的邀请函中,比如他需要知道派对的主题是什么。这就是 $attrs 的作用,它会接收所有没有被子组件声明的属性。

在我们的例子中,父组件只传递了 namelocation 两个属性,但是我们在子组件中声明了这两个属性为 props,所以 $attrs 中并不会包含 namelocation

如果我们再给父组件增加一个属性,比如 theme: 'Hawaiian',然后我们没有在子组件的 props 中声明 theme,那么在子组件中,$attrs 就会包含 { theme: 'Hawaiian' }

也就是说,$attrs 包含的是那些你传递给子组件,但子组件并没有声明的属性。这样的设计使得你可以在不修改子组件的情况下,向子组件传递额外的数据。

如果你在子组件的模板中渲染 {{ $attrs }},那么你会看到这些未被声明的属性。在我们的例子中,你会看到 { theme: 'Hawaiian' }

因此在子组件中,你可以这样使用 $attrs

<template>
  <div>
    <h1>Welcome to the party, {{ name }}!</h1>
    <p>The party is at: {{ location }}</p>
    <p>Other details: {{ $attrs }}</p>
  </div>
</template>

<script>
export default {
  props: {
    name: String,
    location: String
  }
}
</script>

我们还是用派对的例子来解释 $listeners。你可以把 $listeners 理解成子组件监听来自父组件的"通知"或"指示"。

假设现在父组件是派对的发起者,而子组件是被邀请的朋友。父组件可能会给子组件(朋友们)发送一些消息,比如"派对开始了","大家来跳舞","派对结束了"等。这些消息就可以通过 $listeners 在子组件中接收到。

如果父组件发出了一个名为 "dance" 的事件(也就是告诉大家开始跳舞了),子组件可以通过 $listeners 来监听这个事件,然后执行相应的动作(比如开始跳舞)。

<!-- 在父组件中 -->
<child-component @dance="startDancing"></child-component>

<!-- 在子组件中 -->
<template>
  <button @click="$listeners['dance']">开始跳舞</button>
</template>

在这个例子中,父组件通过 @dance="startDancing" 发出 "dance" 事件,子组件通过 $listeners['dance'] 来监听这个事件,当事件发生时(也就是按钮被点击时),开始跳舞。

也就是说,$listeners可以让你在子组件中直接访问和调用父组件中定义的事件处理函数。

所以,当你在子组件的模板中写 @click="$listeners['dance']" 时,意味着当点击按钮时,要执行父组件中名为 "dance" 的事件处理函数。

注意:在 Vue 3 中,$listeners 已被移除,你可以直接在模板或者 setup() 函数中使用 v-on 或者 @ 来监听事件。