Make writing a habit together! This is the fifth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

preface

Componentization is one of the important ideas of Vue framework, in the front-end framework has not appeared, usually a website page is a file, if a page has any data we need to use, directly declare a global variable is good. However, after the emergence of Vue framework, a page is componentized, which means that a page is divided into many files, so the sharing of data between components has become a big problem. Of course, Vue provides many methods for realizing data sharing between components. Today, we will sort out what methods are there?

Because there are only a few commonly used projects, so there are often many partners in the interview cannot say all, so I still suggest a good reason.

1. Which scenarios require communication?

Since all components of Vue are in the form of a component tree, there are many kinds of communication between components, including the following:

  • Communication between parent and child components
  • Communication between sibling components
  • Intergenerational component communication
  • There is no communication between related components

The recommended communication mode varies with each scenario. Select the most appropriate communication mode for components based on the scenario.

2. Props and $emit

This method is usually used to pass values between parent and child components. The parent component passes values to its child components as properties, and the child components receive values through props. The child component passes data to the parent through events.

Initialization project:

We set up a simplest Vue project with three components: parent, child1, and child2. Then we introduce the parent component into app. Vue, and two sub-components, child1 and child2, are introduced into the parent component. The initial running interface is as follows:

2.1 Parent component passes value to child component

Next we use properties to pass values from the parent to the child.

Example code for the parent component:

// SRC /views/ parent-vue <template> <div class="parent-box"> <p> <div> <button @click="changeMsg"> </div> <child1 :msg="msg"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; Export default {data() {return {MSG: "I am the data of parent component ",}; }, components: {child1, child2,}, methods: {// click the button to change the data changeMsg() {this.msg = "become a little bit of a little bit of a class "; ,}}}; </script>Copy the code

We pass the MSG in the parent component to the child component with: MSG =” MSG “and modify the MSG in the parent component when the button is clicked.

Sample code for sub-components:

Vue <template> <div class="child-1"> <p> Child1 </p> <div> <p> Parent {{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: "", }, }, }; </script>Copy the code

The child component receives data from the parent through the props property.

Output result:

When we click the button, the parent component’s data changes, and the child component’s data changes with it.

Note: : MSG =” MSG “is a variable, you can refer to the use of bind, do not add: to receive a string.

2.2 Child Components pass values to parent components

The child component can pass values to the parent through a $emit custom event that the parent listens for to receive values from the child component.

Example code for the parent component:

// SRC /views/ parent-vue <template> <div class="parent-box"> <p> <div> <button @click="changeMsg"> </div> <div> child component data: {{ childData }}</div> <child1 :msg="msg" @childData="childData"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; Export default {data() {return {MSG: "I am the data of parent component ", childData: "",}; }, components: {child1, child2,}, methods: {changeMsg() {this.msg = ""; }, // Listen for the child event childData(data) {this.childdata = data; ,}}}; </script>Copy the code

Sample code for sub-components:

Vue <template> <div class="child-1"> <p> Child1 </p> <div> <button </button> </div> <p> Parent component data: {{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: },}, methods: {// use $emit to send sendData() {this.$emit("childData", "I am childData"); ,}}}; </script>Copy the code

Output result:

In the parent component, we listen for the childData event by @childData=”getChildData” to get the data passed by the child component. The child component sends the data to the parent by clicking the button to trigger the $emit event. When we click the button “Pass data to Parent”, the parent component gets the data.

3.$parent gets the parent component value

This approach makes it very easy for the child component to get the value of the parent component, not only data, but also methods.

Sample code for sub-components:

Vue <template> <div class="child-1"> <p> Child1 </p> <div> <button </button @click="getParentData"> use $parent</button> </div> <p>parent Component data: {{MSG}}</p> </div> </div> </template> <script> export default {props: {MSG: {type: String, default: ", "}}, the methods: {sendData publishes the event () {enclosing $emit (" childData ", "I am a child component data"); $parentData () {console.log(" parent ", this.$parent); ,}}}; </script>Copy the code

Get the properties or data of the parent component from parent when clicking the “Use Parent” button, through the parent button, or through the parent button.

Output result:

We can see that the console prints out all the properties of the parent component, including not only the data data, but also the methods defined in it.

4.$childrenand$refsGets the child component values

These two methods are very similar to $parent in that they can directly retrieve attributes or methods of child components, not just data.

Example code for the parent component:

// SRC /views/ parent-vue <template> <div class="parent-box"> <p> <div> <button @click="changeMsg"> </div> < button@click ="getChildByRef"> use $children and $refs</button> </div> <div> {{ childData }}</div> <child1 ref="child1" :msg="msg" @childData="getChildData"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; Export default {data() {return {MSG: "I am the data of parent component ", childData: "",}; }, components: {child1, child2,}, methods: {changeMsg() {this.msg = ""; }, // Listen for the child's custom event getChildData(data) {this.childdata = data; }, // use $chilren and $refs to get child components getChildByRef() {console.log(" use $children", this. Console. log(" use $refs", this.$refs.child1); ,}}}; </script>Copy the code

Output result:

In the previous code, we clicked the button to get the child component data by children and children and children and refs respectively. Note that children returns an array of all the subcomponents contained in the current component. Using children returns an array of all the subcomponents contained in the current component. Using refs, you need to add the ref property to the subcomponent. It’s a bit like getting a DOM node directly.

5. Use$attrsand$listeners

$attrs was introduced after Vue2.4.0 and is usually used when data is passed between multi-tier components. Many friends may directly choose Vuex for data transmission if they encounter a scenario of multi-layer component data transmission. However, if the data to be transmitted does not involve data update and modification, it is recommended to use $ARrts, because Vuex is still heavy.

The official website explains:

Contains attribute bindings (except class and style) that are not recognized (and retrieved) as prop in the parent scope. When a component does not declare any prop, all parent-scoped bindings (except class and style) are included, and internal components can be passed in via V-bind =”$attrs” — useful when creating higher-level components.

The explanation of the official website is still difficult to understand, we can use a little more popular words to explain again.

Popular explanation:

When a parent component passes a lot of data to a child component, and the child component does not declare props to receive it, the attrs attribute in the child component contains all data from the parent component (except for the one that the props has declared). The child component can also use the v−bind=”attrs “attribute to contain all data from the parent component (except those declared for props). A child component can also use v−bind=”attrs” to pass data to its child (grandchild), which uses $attrs in a manner similar to its parent.

Create a new component, child1-child.vue, that looks like this:

5.1 Use of $ATTRs

We pass a little more data to child1 in the parent component.

Example code for the parent component:

// SRC /views/ parent-vue <template> <div class="parent-box"> <p> <child1 ref="child1" : MSG =" MSG ":msg1="msg1" :msg2="msg2" :msg3="msg3" :msg4="msg4" @childData="getChildData" ></child1> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; Export default {data() {return {MSG: "PARENT data 1", MSg1: "parent data 2", MSG3: "parent data 3", MSg4: "Parent data 4", childData: "",}; }, components: { child1, child2, } }; </script>Copy the code

Here we’ve removed some code that we don’t need in this tutorial, so be aware of that.

Sample code for the CHILD1 component:

// SRC /views/child1.vue <template> <div class="child-1"> <p> Child1 </p> <! -- Child1-child --> <child1-child v-bind="$attrs"></child1-child> </div> </template> <script> import Child1Child from  "./child1-child"; export default { components: { Child1Child, }, props: { msg: { type: String, default: },}, mounted() {console.log("child1 ", this.$attrs); }}; </script>Copy the code

Output result:

In the previous code, our parent component passed five pieces of data to the children: MSG, MSG1, MSG2, MSG3, msG4. But in the props property of the child component, we only receive MSG. Mounted and $attrs is missing the MSG data that props received.

After receiving a component with attrs in child1, we can use v−bind=”attrs” to pass the component to its child child1-child, We added v-bind in the previous code.

Example code for child1-child component:

// SRC /views/ child1-child-vue <template> <div class="child1-child"> <script> export default { props: { msg1: { type: String, default: },}, {console.log("child1-child $attrs", this.$attrs); }}; </script>Copy the code

Output result:

We found msg1 missing from the $attrs printed in the CHILD1-Child component because we had already received msg1 in props.

5.2 use of $Listeners

Listeners and listeners and listeners and attrs and types, but they convey different things.

The official website explains:

Contains v-ON event listeners in the parent scope (without the.native modifier). Internal components can be passed in via V-on =”$Listeners “– useful for creating higher-level components.

Popular explanation:

When a parent component defines custom non-native events on a child component, these custom events are available within the child component via the $Listeners property.

The difference between attrs and $Listeners is obvious. Attrs is used to pass attributes, and $listeners are used to pass non-native events.

Sample code for the CHILD1 component:

// SRC /views/child1.vue mounted() {console.log("child1 gets $attrs", this.$attrs); Console. log(" $listeners", this.$listeners); },Copy the code

Output result:

You can see the output of the childData method, which is our custom listening event in its parent component. In addition, $Listeners can be re-routed to the underlying components as V-Ons.

Sample code for the CHILD1 component:

Vue <template> <div class="child-1"> <p> Child1 </p> <div> <button </button @click="getParentData"> use $parent</button> </div> <p>parent component data: {{MSG}}</p> </div> <! Child --> < listeners --> < listeners --> < listeners --> < listeners --> < listeners -->Copy the code

Example code for child1-child component:

// SRC /views/child1-child.vue () {console.log("child1-child $attrs", this.$attrs); // SRC /views/child1-child.vue () {console.log("child1-child $attrs", this. Console. log(" Child1-child component $listerners", this.$listeners); },Copy the code

Output result:

You can see that the childData custom event from the parent component is also obtained in the child1-Child grandchild component. The advantage of using listeners is that they are not needed if there are multi-tier components: If you have a multi-tiered component, you don’t need to emit events up the chain, you can just use $listerners to get custom events from the parent component.

5.3 inheritAttrs

If you are careful, you will notice that when we use $attrs, the DOM node rendered by the child1 child component renders the attributes we passed together, as shown below:

This is not what we want, and to fix this, we can set the inheritAttrs attribute in the child component.

The official website explains:

Attribute bindings, which are not recognized as props in the parent scope, are “rolled back” by default and applied to the root element of the child component as normal HTML attributes. This may not always behave as expected when writing a component that wraps one target element or another. These default behaviors are removed by setting the inheritAttrs to false. These attributes are enabled by the (also new in 2.4) instance Property $attrs, which can be explicitly bound to non-root elements by V-bind.

The official website said a lot, but not too popular, we can simply understand.

Popular explanation:

The parent component passes a lot of data to the child component, and the props of the child component doesn’t fully receive the data, so the inheritAttrs passed by the parent component are rendered to HTML. We can set the inheritAttrs to false for the child component to avoid rendering.

Sample code for the CHILD1 component:

// src/views/child1.vue
props: {
  msg: {
    type: String,
    default: "",
  },
},
inheritAttrs: false,
Copy the code

Output result:

At this point we have no extraneous node attributes on the node.

5.4 summarize

  • $attrs: Used to pass attributes, class, style, etc., is an object.
  • $listeners are used to record events, but native events are also objects.
  • Attrs and Attrs and attrs and Listeners solve the problem of data and event transfer between multiple layers of components.
  • InheritAttrs resolves attribute rendering of data received without using props.

6. Custom events: Event bus

When we are working on a project, we will find it troublesome to transfer data between unrelated components, such as sibling components and cross-level components. When Vuex is not used, we can use custom events (also known as event centers) to achieve data transfer.

The idea of event center is also relatively simple: the middle center mainly has two functions: triggering events and listening events. If data needs to be passed between two components, component A can trigger events in the event center, and component B can listen for events in the event center, so that the two components can be associated to achieve data transfer.

Implementation steps:

To keep the demonstration simple, we’ll register an event center globally and modify main.js.

The main.js code looks like this:

// src/main.js
Vue.config.productionTip = false
Vue.prototype.$EventBus = new Vue()
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
Copy the code

Sample code for the CHILD1 component:

<template> <div class="child-1"> <p>child1 </p> <div> < button@click ="toChild2"> send data toChild2 </button> </div> </div> </template> <script> import Child1Child from "./child1-child"; Export default {methods: {toChild2() {this.$EventBus.$emit("sendMsg", "I am sending data from child1 "); ,}}}; </script>Copy the code

Child1 component invokes the EventBus. EventBus. EventBus. Emit to add sendMsg center events, this usage is a little similar to the props and $emit.

Example code for child2 component 2:

Vue <template> <div class="child-2"> <p> Child2 </p> <div> <p> Parent {{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: ", "}}, mounted () {$on this. $EventBus. (" sendMsg (MSG) = > {the console. The log (" receives the child1 send data ", MSG); }); }}; </script>Copy the code

When we click the button in the Child1 component, the sendMsg event is triggered. In the Child2 component, we listen for this event, so we receive data from the Child1 component.

Output result:

The way the event center implements data transfer is essentially a publisher and subscriber pattern that allows any component to communicate.

7. Dojo.provide and inject

These two apis are newly added in Vue2.2.0. Provide and Inject need to be used together. They can also enable data communication between components, but you need to ensure a parent-child relationship between components.

The official website explains:

This pair of options needs to be used together to allow an ancestor component to inject a dependency into all of its descendants, regardless of how deep the component hierarchy is, and remain in effect for as long as its upstream and downstream relationships are established.

A parent component can inject a dependency into a child component (regardless of its level), and each child component can obtain this dependency, regardless of its level.

Parent example code:

// src/views/parent.vue <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { provide() { return { parentData: this.msg }; }, data() {return {MSG: "PARENT data 1", MSg1: "parent data 2", MSG3: "parent data 3", MSg4: "Parent data 4", childData: "",}; }, components: { child1, child2, }, }; </script>Copy the code

Example code for child1-child component:

// SRC /views/child1-child.vue <template> <div class="child1-child"> {{parentData}}</p> </div> </template> <script> export default { inject: ["parentData"], props: { msg1: { type: String, default: "",},}, mounted() {console.log("child1-child component $attrs", this. Console. log(" Child1-child component $listerners", this.$listeners); Console. log("child1-child component retrieves parent component data ", this.parentData)},}; </script>Copy the code

Output result:

By combining provide and inject, we obtain data from parent component in child1-Child component. If you go down and try this, you may find that the data is not responsive, that is, the parent component changes the data, and the data in the child1-Child component is not updated.

To become responsive, we need to change the way provide is passed.

The parent code is as follows:

// src/views/parent.vue <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { provide() { return { parentData: this.getMsg }; }, data() {return {MSG: "PARENT data 1", MSg1: "parent data 2", MSG3: "parent data 3", MSg4: "Parent data 4", childData: "",}; }, components: {child1, child2,}, methods: {getMsg() {return this. ,}}}; </script>Copy the code

At this point we’ll see that the data becomes responsive.

The principle of Porvide and Inject can be seen in the following figure:

8. Vuex and localStorage

These two methods should be the most used by friends in practical projects, so here not only expand on the details, just mention the difference between the two.

Vuex:

  • Vuex is a state manager that stores data that is not persistent and disappears when a page is refreshed or a project is closed.
  • Vuex stores data that is responsive.

Localstorage:

  • LoacalStorage is a data storage method in HTML5, persistent storage, storage data is not responsive.

9.v-model

V-model is a built-in directive in VUE that is usually used on form elements to achieve two-way data binding. It is essentially the syntactic sugar of V-ON and V-bind. Here we can also use it to achieve data transfer in some scenarios. Note that the scenario here must be parent-child components.

Example code for the parent component:

<template> <div class="parent-box"> <p> Parent </p> <div>modelData:  {{modelData}}</div> <child2 :msg="msg" v-model="modelData"></child2> <! -- actually equivalent to --> <! -- <child2 v-bind:value="modelData" v-on:input="modelData=$event"></child2> --> </div> </template> <script> import child2 from "./child2.vue"; export default { provide() { return { parentData: this.getMsg }; }, data() {return {modelData: "parent component model data "}; }, components: { child1, }, }; </script>Copy the code

Example code for a child2 component:

<template> <div class="child-2"> <p> Child2 component </p> < button@click ="confirm"> Modify v-model data </button> </div> </div> </template> <script> export default { props: { value: { type: String, default: },},}, console.log("child2 ", this.value) {console.log("child2 ", this.value); }, methods: {// Emit an input event from the parent component and pass the second parameter as a value to the parent component confirm() {this.$emit("input", "modify the v-model data passed by the parent component "); ,}}}; </script>Copy the code

The parent component uses the V-model to pass data to the child component. The props component uses the default value attribute to receive data. The child component uses $emit to trigger the default input event in the parent component.

If you want to learn more about the use of V-Model, please refer to the official website.

conclusion

There are many ways for Vue components to communicate, and each application scenario may be different. Therefore, we need to choose the appropriate communication mode in the appropriate scenario.

  • Communication between parent and child components: props and emit, emit, emit, parent, Refs and refs and children, V-model
  • Communication between sibling components: event bus, Vuex, localStorage
  • Intergenerational component communication: provide and inject
  • Communication between related components: event bus, Vuex, and localStorage