面试官:说说Vue组件间通信有哪些方式(总结了6种)

·  阅读 27815
面试官:说说Vue组件间通信有哪些方式(总结了6种)

前言

当组件间通信的逻辑较为简单时,使用 Prop 和自定义事件足以应对;

但是当出现全局共享的状态、兄弟组件间通信等场景时,使用 Prop 和自定义事件可能会让逻辑变得非常复杂。这个时候,你应该考虑使用一个全局状态管理方案,例如 Vuex。

当然,Vue 组件间通信的通信方式远不止以上两种,下面分别讨论。

一、props down, events up

通常情况下,子组件不处理业务逻辑,只向上派发事件,所以,父子组件间经常需要进行数据传递。

在 Vue 中,父子组件的关系可以总结为 props down, events up,基本流程是:

  • 在父组件中,通过 Prop 向子组件传递数据
  • 在子组件中,通过触发(emit)一个自定义事件,然后在父组件中使用 v-on 进行监听

下面是一个使用 Prop 方式通信的一个 TodoList 示例:

<!-- 父组件 Todo.vue -->
<template>
  <ul class="todo-list">
    <TodoItem v-for="item in todos" :key="item.id" :item="item" @remove="removeItem" />
  </ul>
</template>

<script>
import TodoItem from './components/TodoItem.vue'

export default {
  components: {
    TodoItem,
  },
  data() {
    return {
      todos: [
        { id: 1, text: 'Learning Vue' },
        { id: 2, text: 'Learning React' },
        { id: 3, text: 'Learning Node.js' },
      ],
    }
  },
  methods: {
    removeItem(id) {
      this.todos = this.todos.filter((i) => i.id !== id)
    },
  },
}
</script>
复制代码
<!-- 子组件 TodoItem.vue -->
<template>
  <li @click="remove">{{ item.text }}</li>
</template>

<script>
export default {
  props: ['item'],
  methods: {
    remove() {
      this.$emit('remove', this.item.id)
    },
  },
}
</script>
复制代码

二、Vuex

Prop 和自定义事件这种“单向数据流”的方式只适合用来解决简单场景下的组件间通信,当出现下列场景时,使用 Prop 处理起来可能非常棘手:

  • 兄弟组件间的状态传递
  • 多层嵌套组件间的状态传递
  • 全局共享的状态

当出现上述场景时,你应该使用一个全局状态管理方案,例如 Vuex。

Vuex 是一个专为 Vue.js 应用程序开发的集中式状态管理库。核心概念是:

  1. State:即 Vuex 维护的全局状态,它是一颗单一状态树(用一个对象包含了全部的状态),并且这些状态是响应式的。
  2. Mutation:即同步事务,改变 store 中的状态的唯一途径就是显式地提交 mutation,这样使得我们可以方便地跟踪每一个状态的变化。
  3. Action:即异步事务,Action 通过提交的 mutation 来间接地改变状态,而不是直接变更状态(State)

vuex

三、vm.$root

我们可以将全局状态挂载到 Vue 根实例上,如下所示:

// main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  el: '#app',
  render: (h) => h(App),
  data: {
    theme: 'light',
  },
  methods: {
    setTheme(newValue) {
      console.log('setTheme triggered with', newValue)
      this.theme = newValue
    },
  },
})
复制代码

然后,你就可以在任意子组件中通过 this.$root 来调用根实例上的属性和方法,如:this.$root.themethis.$root.setTheme(),你甚至可以直接在子组件中对根实例上面的属性重新赋值,如 this.$root.theme = 'dark'

于是乎,这个根实例就成为了一个全局 store。

这很方便,但这也带来了一些问题,比如你不能很方便地跟踪每一个状态的变化,你也不能很好地将 store 划分为多个模块。

所以,对于非常小型的应用,使用这种方式确实很方便;但是在中大型应用中,强烈推荐使用 Vuex 来管理应用的状态。

我们应该避免在应用中使用 vm.$root / vm.$parent / vm.$children,因为这种强关联背离了组件的解耦原则,也会让状态的流向变得不可控。

四、provide / inject

当我们需要向更深层级的组件传递信息时,使用 Prop 和 $parent 都不太方便。

Vue 提供了一种能力,让我们可以向任意更深层级的组件提供上下文信息 —— provide / inject,它被称为“依赖注入”,你可以理解为“大范围有效的 prop”。

  • provide 选项允许我们指定我们想要提供给后代组件的数据/方法
  • 然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property
// 祖先组件
export default {
  provide() {
    return {
      theme: 'light',
    }
  },
}

// 后代组件
export default {
  inject: ['theme'],
  mounted() {
    console.log(this.theme)
  },
}
复制代码

然而,依赖注入还是有负面影响的:

  • 它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
  • 同时所提供的 property 并不是响应式的。

provide 和 inject 主要在开发高阶插件/组件库时使用,并不推荐用于普通应用程序代码中。

五、Event Bus

Event Bus 又叫“全局事件总线”,是一个简易的全局状态管理器。

具体的做法是:实例化一个空的 Vue 实例来作为事件中心,然后在组件中,可以使用 $emit$on$off 分别来分发、监听、取消监听事件

// event-bus.js
import Vue from 'vue'

const EventBus = new Vue({
  data: {
    count: 0,
  },
  created() {
    // 监听自定义事件, 事件可以由 vm.$emit 触发
    this.$on('increase', this.increase)
  },
  // 最好在组件销毁前, 清除事件监听
  beforeDestroy: function () {
    this.$off('increase', this.increase)
  },
  methods: {
    increase(count) {
      this.count += count
    },
  },
})

export default EventBus
复制代码

然后,你可以在组件中这样使用:

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increase(10)">Increase</button>
  </div>
</template>

<script>
import EventBus from '../event-bus'

export default {
  computed: {
    count() {
      return EventBus.count
    },
  },
  methods: {
    increase(count) {
      EventBus.$emit('increase', count)
    },
  },
}
</script>
复制代码

六、Vue.observable(object)

Vue.observable(object) 是 2.6.0 新增的一个全局 API,用于让一个对象变成响应式的,Vue 内部会用它来处理 data 函数返回的对象。

与 Event Bus 类似,我们也可以使用 Vue.observable(object) 来作为最小化的全局状态管理器,用于简单的场景:

// store.js
import Vue from 'vue'

// 模拟 Vuex Store
export const store = Vue.observable({
  count: 0,
})

// 模拟 Vuex mutations
export const mutations = {
  increase(count) {
    store.count += count
  },
}
复制代码

然后,你可以在组件中这样使用:

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increase(10)">Increase</button>
  </div>
</template>

<script>
import { store, mutations } from '/path/to/store'

export default {
  computed: {
    count() {
      return store.count
    },
  },
  methods: {
    increase: mutations.increase,
  },
}
</script>
复制代码

后记

如果文中出现纰漏,或者您有其他的更好的解决方案,欢迎留言讨论 🐤

参考资料:

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改