Six ways of Vue component communication

Posted by superman on Wed, 02 Feb 2022 06:17:55 +0100

preface

The component is Vue JS is one of the most powerful functions, and the scope of component instances is independent of each other, which means that the data between different components cannot refer to each other. Generally speaking, components can have the following relationships:

As shown in the above figure, A and B, B and C, B and D are all parent-child relationships, C and D are siblings, and A and C are intergenerational relationships (possibly multiple generations).

How to choose effective communication methods for different use scenarios? This is the subject we want to explore. This paper summarizes several ways of communication between vue components, such as props, $emit/$on, vuex, $parent / $children, $attrs/$listeners and provide / input, and describes the differences and usage scenarios with easy to understand examples, hoping to be of some help to small partners.

Please stamp the code of this article github blog , you'll feel shallow on paper. Let's knock more codes!

Method 1: props/$emit

Parent component a passes to child component B through props, and B to A is realized by $emit in component B and v-on in component A.

1. Transfer value from parent component to child component

Next, we use an example to illustrate how the parent component passes values to the child component: in the child component users How to get the parent component app in Vue Data users in Vue: ["Henry", "Bucky", "Emily"]

//App.vue parent component
<template>
  <div id="app">
    <users v-bind:users="users"></users>//The former custom name is convenient for sub components to call, while the latter needs to pass the data name
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}
Copy code
//users subcomponent
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}</li>//Traverse the passed value and render it to the page
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    users:{           //This is the custom name of the child tag of the parent component
      type:Array,
      required:true
    }
  }
}
</script>
Copy code

Summary: the parent component passes data down to the child component through props. Note: there are three forms of data in the component: data, props and calculated

2. Pass values from child components to parent components (in the form of events)

Next, we use an example to illustrate how a child component transfers values to a parent component: when we click "Vue.js Demo", the child component transfers values to the parent component, and the text changes from "transferring a value" to "transferring a value from a child to a parent component", so as to realize the transfer of values from a child component to a parent component.

// Subcomponents
<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//Bind a click event
  </header>
</template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","Pass values from parent to child components");//Custom event pass value "pass value from child to parent component"
    }
  }
}
</script>
Copy code
// Parent component
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" ></app-header>//Consistent with the sub component titleChanged custom event
   // updateTitle($event) accepts the passed text
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"A value is passed"
    }
  },
  methods:{
    updateTitle(e){   //Declare this function
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>
Copy code

Summary: the child component sends messages to the parent component through events. In fact, the child component sends its own data to the parent component.

Method 2: $emit/$on

This method uses an empty Vue instance as the central event bus (event center), which is used to trigger and monitor events, and cleverly and lightly realizes the communication between any component, including parent-child, brother and cross level. When our project is relatively large, we can choose a better state management solution vuex.

1. Specific implementation method:

    var Event=new Vue();
    Event.$emit(Event name,data);
    Event.$on(Event name,data => {});
Copy code

2. For example

Suppose there are three sibling components, namely A, B and C components. How can C component obtain the data of A or B components

<div id="itany">
	<my-a></my-a>
	<my-b></my-b>
	<my-c></my-c>
</div>
<template id="a">
  <div>
    <h3>A Components:{{name}}</h3>
    <button @click="send">Send data to C assembly</button>
  </div>
</template>
<template id="b">
  <div>
    <h3>B Components:{{age}}</h3>
    <button @click="send">Send array to C assembly</button>
  </div>
</template>
<template id="c">
  <div>
    <h3>C Components:{{name}},{{age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//Define an empty Vue instance
var A = {
	template: '#a',
	data() {
	  return {
	    name: 'tom'
	  }
	},
	methods: {
	  send() {
	    Event.$emit('data-a', this.name);
	  }
	}
}
var B = {
	template: '#b',
	data() {
	  return {
	    age: 20
	  }
	},
	methods: {
	  send() {
	    Event.$emit('data-b', this.age);
	  }
	}
}
var C = {
	template: '#c',
	data() {
	  return {
	    name: '',
	    age: ""
	  }
	},
	mounted() {//Execute after template compilation
	 Event.$on('data-a',name => {
	     this.name = name;//No new this will be generated inside the arrow function. If not = >, this refers to Event
	 })
	 Event.$on('data-b',age => {
	     this.age = age;
	 })
	}
}
var vm = new Vue({
	el: '#itany',
	components: {
	  'my-a': A,
	  'my-b': B,
	  'my-c': C
	}
});	
</script>
Copy code

$on listens to the custom events data-a and data-b, because it is sometimes uncertain when the event will be triggered. It is usually monitored in the mounted or created hook.

Method III. vuex

1. Briefly introduce the principle of Vuex

Vuex implements a one-way data flow and has a State to store data globally. When a component wants to change the data in the State, it must be done through mutation. Mutation also provides a subscriber mode for external plug-ins to call to obtain the update of State data. However, when all asynchronous operations (common in calling back-end interfaces to obtain update data asynchronously) or batch synchronous operations need to go through the Action, but the Action cannot directly modify the State, it still needs to modify the data of the State through the mutation. Finally, render to the view according to the change of State.

2. Briefly introduce the functions of each module in the process:

  • Vue Components: Vue Components. On the HTML page, it is responsible for receiving user operations and other interactive behaviors, executing the dispatch method and triggering the corresponding action to respond.
  • dispatch: operation behavior trigger method, which is the only method that can execute action.
  • Actions: operation behavior processing module, which is composed of $store in the component Dispatch ('action name ', data1) to trigger. Then commit() triggers the call of mutation to update the state indirectly. Be responsible for handling all interaction behaviors received by Vue Components. It contains synchronous / asynchronous operations and supports multiple methods with the same name, which are triggered in turn according to the registration order. The operations requested from the background API are carried out in this module, including triggering other actions and submitting mutation. This module provides Promise encapsulation to support chain triggering of action.
  • commit: state change submission operation method. Submitting a mutation is the only way to execute a mutation.
  • Changes: state change operation method, which is triggered by commit ('change name ') in actions. It is the only recommended way for Vuex to modify the state. This method can only perform synchronization operations, and the method name can only be globally unique. During the operation, some hook s will be exposed for state monitoring.
  • State: page state management container object. The scattered data of data objects in Vue components are stored in a centralized way, which is globally unique for unified state management. The data required for page display is read from the object, and Vue's fine-grained data response mechanism is used to update the status efficiently.
  • getters: state object reading method. The module is not listed separately in the figure and should be included in render. Vue Components reads the global state object through this method.

3.Vuex and localStorage

Vuex is the state manager of vue, and the stored data is responsive. However, it will not be saved. After refreshing, it will return to the initial state. The specific method should copy and save a copy of the data in vuex to localStorage when the data changes. After refreshing, if there is saved data in localStorage, take it out and replace the state in the store.

let defaultCity = "Shanghai"
try {   // The user has turned off the local storage function. At this time, a try is added to the outer layer catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // When the data changes, a copy of the data is saved in localStorage
      } catch (e) {}
    }
  }
})
Copy code

It should be noted here that in vuex, all the saved states are arrays, while localStorage only supports strings, so JSON conversion is required:

JSON.stringify(state.subscribeList);   // array -> string
JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array 
Copy code

Method 4: $attrs/$listeners

1. Introduction

When multi-level component nesting needs to transfer data, the usual method is through vuex. But if you just transfer data without intermediate processing, using vuex processing is a bit overqualified. For this purpose, vue2 Version 4 provides another method - $attrs/$listeners

  • $attrs: contains attribute bindings (except class and style) in the parent scope that are not recognized (and obtained) by prop. When a component does not declare any prop, the binding of all parent scopes (except class and style) will be included here, and the internal components can be passed in through v-bind="$attrs". Usually used in conjunction with the inheritAttrs option.

  • $listeners: contains the v-on event listeners in the parent scope (excluding the. native modifier). It can pass in internal components through v-on="$listeners"

Next, let's look at an example of cross level communication:

// index.vue
<template>
  <div>
    <h2>Boating in the waves</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="Front end craftsman"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>
Copy code
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1 of $attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // You can turn off attributes that are not declared in props that are automatically mounted to the component root element
  props: {
    foo: String // foo is bound as props attribute
  },
  created() {
    console.log(this.$attrs); // {"boo": "Html", "coo": "CSS", "doo": "Vue", "title": "front end craftsman"}
  }
};
</script>
Copy code
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // {"coo": "CSS", "doo": "Vue", "title": "front end craftsman"}
  }
};
</script>
Copy code
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>
Copy code

As shown in the above figure, $attrs represents an object that does not inherit data. The format is {attribute name: attribute value}. Vue2.4 provides $attrs and $listeners to transfer data and events, which makes the communication between cross level components easier.

To put it simply: $attrs and $listeners are two objects, $attrs stores the non Props attribute bound in the parent component, and $listeners stores the non-native events bound in the parent component.

Method 5: provide/inject

1. Introduction

Vue2.2.0 added API, which needs to be used together with options to allow an ancestor component to inject a dependency into all its descendants, no matter how deep the component level is, and it will always take effect when the upstream and downstream relationship is established. In a nutshell: the ancestor component provides variables through the provider, and then injects variables through the inject in the descendant component. The provide / inject API mainly solves the communication problem between cross level components, but its usage scenario is mainly that sub components obtain the status of superior components, and a relationship between active provision and dependency injection is established between cross level components.

2. For example

Suppose that there are two sub components of A.ue and B

// A.vue
export default {
  provide: {
    name: 'Boating in the waves'
  }
}
Copy code
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // Boating in the waves
  }
}
Copy code

As you can see, in A.vue, we set a , provide: name, whose value is boating in the waves. Its function is to provide the variable , name , to all its subcomponents. In B.vue, if the "name" variable provided from component A is injected through "inject", then in component B, you can directly use "this" Name , has accessed this variable, and its value is also boating in the waves. This is the core usage of the provide / inject API.

It should be noted that the provide and inject bindings are not responsive. This is deliberate. However, if you pass in an object that can be listened to, its object properties are still responsive - Vue official document. Therefore, if the name of A.vue is changed, B.vue's "this" Name , won't change. It's still boating in the waves.

3. How do provide and inject realize data response

Generally speaking, there are two ways:

  • provide the instance of the ancestor component, and then inject dependencies into the descendant component, so that the attributes of the instance of the ancestor component can be directly modified in the descendant component. However, one disadvantage of this method is that many unnecessary things such as props and methods are mounted on this instance
  • Use the latest API Vue Observable optimized responsive provide (recommended)

Let's take an example: Sun components D, E and f obtain the color value passed by component A and can realize data responsive change, that is, after the color of component a changes, components D, E and F will change accordingly (the core code is as follows:)

// A component 
<div>
      <h1>A assembly</h1>
      <button @click="() => changeColor()">change color</button>
      <ChildrenB />
      <ChildrenC />
</div>
......
  data() {
    return {
      color: "blue"
    };
  },
  // provide() {
  //   return {
  //     theme: {
  //       color: this.color / / the data bound in this way is not responsive
  //     }/ / that is, after the color of component A changes, components D, E and F will not change
  //   };
  // },
  provide() {
    return {
      theme: this//Method 1: provide an instance of the ancestor component
    };
  },
  methods: {
    changeColor(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
  // Method 2: use the latest API Vue Observable optimized responsive provide
  // provide() {
  //   this.theme = Vue.observable({
  //     color: "blue"
  //   });
  //   return {
  //     theme: this.theme
  //   };
  // },
  // methods: {
  //   changeColor(color) {
  //     if (color) {
  //       this.theme.color = color;
  //     } else {
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
  //     }
  //   }
  // }
Copy code
// F assembly 
<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F assembly</h3>
  </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //Different values of functional components
      default: () => ({})
    }
  }
};
</script>
Copy code

Although provide and inject mainly provide use cases for high-level plug-in / component libraries, if you can skillfully use them in business, you can get twice the result with half the effort!

Method 6: $parent / $children and ref

  • ref: if it is used on an ordinary DOM element, the reference refers to the DOM element; If used on a child component, the reference points to the component instance
  • $parent / $children: access parent / child instances

It should be noted that both of these methods get component instances directly. After use, you can directly call component methods or access data. Let's take a look at an example of accessing components with ref:

// component-a subcomponent
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
Copy code
// Parent component
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // Popup
    }
  }
</script>
Copy code

However, the disadvantage of these two methods is that they cannot communicate across levels or brothers.

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>
Copy code

In component-a, we want to access two component-b components in the page referring to it (here is parent.vue). In this case, we have to configure additional plug-ins or tools, such as Vuex and Bus solutions.

summary

Common usage scenarios can be divided into three categories:

  • Parent child communication:

The parent transfers data to the child through props, and the child transfers data to the parent through events ($emit); You can also communicate through the parent chain / child chain ($parent / $children); ref can also access component instances; provide / inject API;$ attrs/$listeners

  • Brother communication:

Bus;Vuex

  • Cross level communication:

Bus;Vuex;provide / inject API,$attrs/$listeners

Topics: Javascript Front-end Vue.js