preface

When the logic of communication between components is simple, using Prop and custom events is sufficient.

But when there are scenarios like globally shared state, communication between sibling components, and so on, using Prop and custom events can complicate the logic. At this point, you should consider using a global state management scheme such as Vuex.

Of course, there are more ways to communicate between Vue components than the above two, which are discussed separately below.

B, props down, events up

Typically, child components do not handle business logic and only send up events, so there is often a need to pass data between parent and child components.

In Vue, the relationship between parent and child components can be summarized as props down and events up. The basic flow is as follows:

  • In the parent component, data is passed to the child component through Prop
  • In the child component, by firing (emitA custom event that is then used in the parent componentv-onmonitor

Here is an example of a TodoList that communicates using Prop:

<! -- Parent 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>
Copy the code
<! -- Todoitem.vue -->
<template>
  <li @click="remove">{{ item.text }}</li>
</template>

<script>
export default {
  props: ['item'].methods: {
    remove() {
      this.$emit('remove'.this.item.id)
    },
  },
}
</script>
Copy the code

Second, the Vuex

The “one-way data flow” approach of Prop and custom events is only suitable for solving communication between components in simple scenarios, which can be tricky to handle with Prop when:

  • State transfer between sibling components
  • State transfer between multiple layers of nested components
  • Status of the global share

When these scenarios occur, you should use a global state management scheme, such as Vuex.

Vuex is a centralized state management library developed specifically for vue.js applications. The core concepts are:

  1. State: The global State maintained by Vuex, which is a single State tree (containing all states in a single object), and these states are responsive.
  2. Mutation: Synchronous transactions. The only way to change the state in the store is to explicitly commit Mutation, which makes it easy to track each state change.
  3. Action: an asynchronous transaction, where the Action indirectly changes State by mutation of the submitted, rather than directly changing State.

Three,vm.$root

We can mount global state to the Vue root instance as follows:

// 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
    },
  },
})
Copy the code

You can then call properties and methods on the root instance from any child component via this.$root, as in: This.$root.theme, this.$root.settheme (), and you can even reassign the properties above the root instance directly in child components, such as this.$root.theme = ‘dark’.

As a result, the root instance becomes a global store.

This is convenient, but it also introduces some problems, such as you can’t easily track every state change, and you can’t divide the Store into modules very well.

So, for very small applications, it’s really convenient; However, in medium and large applications, Vuex is highly recommended to manage application state.

We should avoid using vm.$root/vm.$parent/vm.

Four,provide / inject

Both Prop and $parent are inconvenient when we need to pass information to higher-level components.

Vue provides the ability to provide context information to any deeper level of components — provide/inject. This is called “dependency injection”, which you can think of as “large scale effective prop”.

  • provideOptions allow us to specify the data/methods we want to provide to future generations of components
  • And then in any descendant component, we can use itinjectOption to receive the specified property that we want to add to this instance
// Ancestor component
export default {
  provide() {
    return {
      theme: 'light',}}}// Descendant components
export default {
  inject: ['theme'].mounted() {
    console.log(this.theme)
  },
}
Copy the code

However, there are downsides to DI:

  • It couples the components in your application to their current organization, making refactoring more difficult.
  • Also, the provided property is not reactive.

Provide and Inject are mainly used when developing high-level plug-ins/component libraries and are not recommended for common application code.

Five, the Event Bus

Event Bus, also called “global Event Bus”, is a simple global status manager.

Instantiate an empty Vue instance as the event center, and then use $emit, $ON, and $OFF to distribute, listen, and cancel listening events, respectively

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

const EventBus = new Vue({
  data: {
    count: 0,},created() {
    // Listen for custom events, which can be emitted by vm.$emit
    this.$on('increase'.this.increase)
  },
  // It is best to clear event listeners before the component is destroyed
  beforeDestroy: function () {
    this.$off('increase'.this.increase)
  },
  methods: {
    increase(count) {
      this.count += count
    },
  },
})

export default EventBus
Copy the code

Then, you can use it in components like this:

<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>
Copy the code

Vi.Vue.observable(object)

Vue.observable(Object) is a global API added in 2.6.0 that makes an object responsive. Vue uses it internally to process objects returned by data.

Similar to the Event Bus, vue.Observable (Object) can be used as a minimal global state manager for simple scenarios:

// store.js
import Vue from 'vue'

// Simulate the Vuex Store
export const store = Vue.observable({
  count: 0,})Vuex mutations
export const mutations = {
  increase(count) {
    store.count += count
  },
}
Copy the code

Then, you can use it in components like this:

<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>
Copy the code

Afterword.

If there is a mistake in this article, or you have other better solution, please leave a comment at 🐤

References:

  • Dependency injection
  • Non-flux global state management
  • Creating a Global Event Bus with Vue.js
  • API – provide / inject
  • API – Vue.observable(object)