Vue is a framework for data-driven view updates, so data communication between components is very important for VUE; The usual way to do this is to pass values to sub-components via props, but vue also has many other ways of communicating that are not commonly used. Understanding them may give you more ideas and options when writing code in the future.

1.. sync modifier

In some cases, we want to be able to “directly modify” the prop value of the parent component in the child component, but bidirectional binding can cause maintenance problems; Vue provides a solution through the syntactic sugar. Sync modifier.

The.sync modifier existed in vu1.x as a bidirectional binding, meaning that a child component could change the value in its parent. However, it violated the design concept of one-way data flow and was killed in VUe2.0. But it was reintroduced in vue2.3.0+ and above. But this time it only exists as a compile-time syntactic sugar. It is extended to a V-ON listener that automatically updates the parent component’s properties. In plain English, let’s manually update the values in the parent component to make it more obvious where the data changes came from.

//Parent.vue <template> <div> <Child :msg.sync="msg" :num.sync="num"></Child> </div> </template> <script> import Child from "./child"; export default { name: "way2", components: { Child }, data() { return { msg: "hello every guys", num: 0 }; }}; </script>Copy the code

We add a.sync modifier to each value passed to the Child component, which is extended at compile time to the following code:

<Child :msg="msg" @update.msg="val => msg = val" :num.sync="num" @update.num="val => num = val"></Child> 
Copy the code

So the child component only needs to display the update event that triggers the update:

// child-vue <template> <div> <div @click="clickRevert"> {{ num }}</div> <div @click="clickOpt('add')" class="opt">+</div> <div @click="clickOpt('sub')" class="opt">-</div> </div> </template> <script> export default { props: { msg: { type: String, default: "" }, num: { type: Number, default: 0 } }, methods: { clickRevert() { let { msg } = this; this.$emit("update:msg",msg.split("").reverse().join("")); }, clickOpt(type = "") { let { num } = this; if (type == "add") { num++; } else { num--; } this.$emit("update:num", num); }}}; </script>Copy the code

Does this “bidirectional binding” operation look familiar? Yes, the V-Model is also essentially a syntactic sugar, except that instead of triggering the update method, it triggers the input method; And the V-model is less flexible than sync, which can only bind one value.

Summary: The.sync modifier optimizes the way parent components communicate with each other, eliminating the need to write extra functions in the parent component to change the assignment.

2.
a t t r s and Attrs and
listeners

When cross-level communication from A to C is needed, we will find that prop transmission is very troublesome and there will be many redundant and tedious forwarding operations. If state changes in C also need to be passed to A, and events need to be passed up level by level, the code becomes even less readable.

Vue2.4 + provides new solutions: $attrs and $Listeners. The description of $attrs contains feature 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.

This paragraph is very convoluted and difficult to understand on the first reading, but it doesn’t matter, let’s go straight to the code:

//Parent.vue <template> <div> <Child :notUse="'not-use'" :childMsg="childMsg" :grandChildMsg="grandChildMsg" @onChildMsg="onChildMsg" @onGrandChildMsg="onGrandChildMsg" ></Child> </div> </template> <script> import Child from "./child"; export default { data() { return { childMsg: "hello child", grandChildMsg: "hello grand child" }; }, components: { Child }, methods: { onChildMsg(msg) { this.childMsg = msg; }, onGrandChildMsg(msg) { this.grandChildMsg = msg; }}}; </script>Copy the code

We first define two MSGS, one for the child component to display, and the other for the child component to display. We first pass these two data to the child component, and pass in two functions that change THE MSG.

//child.vue
<template>
  <div class="box">
    <div @click="clickMsg">{{ childMsg }}</div>
    <div>$attrs: {{ $attrs }}</div>
    <GrandChild v-bind="$attrs" v-on="$listeners"></GrandChild>
  </div>
</template>
<script>
import GrandChild from "./grand-child";
export default {
  props: {
    childMsg: {
      type: String
    }
  },
  methods: {
    clickMsg() {
      let { childMsg } = this;
      this.$emit(
          "onChildMsg",
          childMsg.split("").reverse().join("")
      );
    }
  },
  components: { GrandChild }
};
</script>
Copy the code

In the subcomponent, we use props to get the parameters needed by the subcomponent, that is, childMsg; The remaining parameters are grouped into the $attrs object, which we can display on the page and pass on to the grandchild component; Add all the listeners to $listeners and continue on.

//grand-child.vue <template> <div class="box1" @click="clickMsg">grand-child:{{ grandChildMsg }}</div> </template> <script> export default { props: { grandChildMsg: { type: String } }, methods: { clickMsg() { let { grandChildMsg } = this; this.$emit( "onGrandChildMsg", grandChildMsg.split("").reverse().join("") ); }}}; </script>Copy the code

In the sun component, we continue to take out the required data for display or operation, and the running results are as follows:

! [ Attr – the result] (https://xieyufei.com/images/Vue – Communicate/attr – the result] (https://xieyufei.com/images/Vue-Communicate/attr – r Esult] (https://xieyufei.com/images/Vue – Communicate/attr – result. GIF)

When we assign a non-prop declaration to a component, such as the NotUse and GrandChildMsg attributes on the Child component that we don’t use, the compiled code treats these attributes as if they were original and adds them to the HTML native tag, so we look at the code like this:

We can remove the inheritAttrs attribute by adding it to the component:

export default {
    mounted(){},
    inheritAttrs: false,
}
Copy the code

Note: Attrs and Listeners are great solutions to the problem of sending values across tiers of components.

3. The dojo.provide and inject

Attrs and listeners can easily upload values from parent to parent, but don’t notice if the parent has already retrieved the desired data.

Provide/Inject is a new attribute in Vue2.2 +. Simply put, provide variables in the parent component and inject variables in the child component. Unlike $attrs, inject only one level down; Inject is available no matter how deep the subcomponents are nested.

//Parent.vue <template> <div> <Child></Child> </div> </template> <script> import Child from "./child"; export default { components: { Child }, data() { return { childmsg: "hello child", grandmsg: "hello grand child" }; }, provide() { return { childmsg: this.childmsg, grandmsg: this.grandmsg }; }, mounted() { setTimeout(() => { this.childmsg = "hello new child"; this.grandmsg = "hello new grand child"; }, 2000); }}; </script>Copy the code

We inject two variables in the parent via provide, modify the value of the variables two seconds later, and then fetch them in the child and grandchild components.

//child.vue
<template>
  <div class="box">
    <div>child-msg:{{ childmsg }}</div>
    <div>grand-msg:{{ grandmsg }}</div>
    <GrandChild></GrandChild>
  </div>
</template>
<script>
import GrandChild from "./grand-child";
export default {
  inject: ["childmsg", "grandmsg"],
  components: { GrandChild },
};
</script>
//grand-child.vue
<template>
  <div class="box">
    <div>child-msg:{{ childmsg }}</div>
    <div>grand-msg:{{ grandmsg }}</div>
  </div>
</template>
<script>
export default {
  name: "GrandChild",
  inject: ["childmsg", "grandmsg"],
};
</script> 
Copy the code

You can see that both child and grandchild components can pull out values and render them. Note that once a child component has injected some data, it can no longer be declared in data.

At the same time, after two seconds, we found that the values of childmsg and grandmsg did not change as expected, that is, the child components did not respond to the modified values.

Tip: Provide and Inject binding are not responsive. This is intentional. However, if you pass in a listening object, the object’s properties are still responsive.

Vue does not design provide and inject to be responsive, this is intentional, but if a listener is passed in, it will respond:

export default { data() { return { respondData: { name: "hello respond" } }; }, provide() { return { respondData: this.respondData }; }, mounted() { setTimeout(() => { this.respondData.name = this.respondData.name .split("") .reverse() .join(""); }, 2000); }},Copy the code

4.EventBus

I first translated EventBus directly as EventBus, but the official translation is EventBus. It essentially creates an instance of VUE and Bridges vUE components through an empty vUE instance. It is a solution for non-parent component communication, where all components can notify other components up and down in parallel, but it is so convenient that it can be a “disaster” that is difficult to maintain if used improperly.

First, create an empty Vue object and export it. It’s a component that doesn’t have the DOM. All it has is its instance methods, so it’s very lightweight.

//main.js
import bus from "@/utils/event-bus";
Vue.prototype.$bus = bus; 
Copy the code

Mount it globally and make it a global event bus so that it can be easily invoked in components.

//Parent.vue
<template>
  <div class="box">
    <Child1></Child1>
    <Child2></Child2>
  </div>
</template>
<script>
import Child1 from "./child1";
import Child2 from "./child2";
export default {
  components: {
    Child1,
    Child2
  }
};
</script> 
Copy the code

We start by defining two child components, child1 and child2, that we want to send messages directly to each other.

//child1.vue <template> <div> <div class="send" @click="clickSend"> </div> <template v-for=" index) in msgList"> <div :key="index">{{ item }}</div> </template> </div> </template> <script> export default { data() {  return { msgList: [] }; }, mounted() { this.$bus.$on("getMsg1", res => { this.msgList.push(res); }); }, methods: { clickSend() { this.$bus.$emit("getMsg2", "hello from1:" + parseInt(Math.random() * 20)); }}}; </script> //child2.vue <template> <div> <div class="send" @click="clickSend"> </div> <template v-for="(item, index) in msgList"> <div :key="index">{{ item }}</div> </template> </div> </template> <script> export default { data() {  return { msgList: [] }; }, mounted() { this.$bus.$on("getMsg2", res => { this.msgList.push(res); }); }, methods: { clickSend() { this.$bus.$emit("getMsg1", "hello from2:" + parseInt(Math.random() * 20)); }}}; </script>Copy the code

When we initialize, we register two receive events in child1 and child2, and then click the button to trigger these two custom events and pass in data. Finally, the two components receive the message from each other, and the final effect is as follows:

As mentioned earlier, EventBus can be a disaster if not used properly. What exactly is a disaster? Everyone knows that Vue is a single page app, and if you refresh a page, the EventBus associated with it is removed, which can cause business to fail. In addition, if the business has repeated operations on the page, the EventBus will trigger many times when listening, which is also a very big risk. At this point, we need to deal with the relationship of EventBus in the project. It is often used to remove the EventBus event listener when a page or component is destroyed.

export default{
    destroyed(){
        $EventBus.$off('event-name')
    }
} 
Copy the code

Conclusion: EventBus can be used to easily communicate between sibling and cross-level components, but it can cause problems when used incorrectly. Therefore, vuex is recommended for small pages where the logic is not complex.

5.$refs 

Sometimes we need to manipulate DOM elements directly in the VUE, such as getting the height of divs or calling functions of child components directly. Although native JS is also available, vue provides a more convenient attribute: $refs. If you use it on a normal DOM element, you get the DOM element. If used on a child component, it gets the instance object of the component.

//child.vue <template> <div> initialize :{{num}}</div> </template> <script> export default {data() {return {num: 0}; }, methods: { addNum() { this.num += 1; }, subNum() { this.num -= 1; }}}; </script>Copy the code

We start by creating a simple child component with two functions to increase or decrease the value of num.

<template> <div> <Child ref="child"></Child> <div class="opt" ref="opt_add" @click="clickAddBtn">+</div> <div class="opt" ref="opt_sub" @click="clickSubBtn">-</div> <div class="opt" ref="opt_show" @click="clickShowBtn">show</div> </div> </template> <script> import Child from "./child"; export default { components: { Child }, data() { return {}; }, methods: { clickAddBtn() { this.$refs.child.addNum(); }, clickSubBtn() { this.$refs.child.subNum(); }, clickShowBtn() { console.log(this.$refs.child); console.log(this.$refs.child.num); }}}; </script>Copy the code

We add a ref attribute child to the child, and then use $refs.child to get an instance of the child and call the function in the child from the instance.

! [ Refs. GIF] (https://xieyufei.com/images/Vue – Communicate/refs. GIF] (https://xieyufei.com/images/Vue-Communicate/refs.gif] (ht) tps://xieyufei.com/images/Vue – Communicate/refs. GIF)

As you can see, we get a VueComponent object, which contains all the data and functions of the child component, and can do some operations on the child component.

6.
p a r e n t and The parent and
children 

If a page has multiple sub-components that need to be manipulated one by one, refs provides additional attributes: ‘Refs is cumbersome to operate one by one. Vue provides additional attributes:’ Refs is cumbersome to operate one by one. Vue provides additional attributes: ‘parent’ and $children ‘to unify the selection.

//child.vue <template> <div>child</div> </template> <script> export default { mounted() { console.log(this.$parent.show()); console.log("Child", this.$children, this.$parent); }}; </script> //Parent.vue <template> <div> parent <Child></Child> <Child></Child> </div> </template> <script> import Child from "./child"; export default { components: { Child }, mounted() { console.log("Parent", this.$children, this.$parent); }, methods: { show() { return "to child data"; }}}; </script>Copy the code

We insert two identical children into the parent, call the parent’s function with $parent in the child, and get an array of child instances with $children in the parent.

! [ Children. PNG] (https://xieyufei.com/images/Vue – Communicate/children. PNG] (HTTP: / / https://xieyufei.com/images/Vue-Communicate/chil Dren. PNG] (https://xieyufei.com/images/Vue – Communicate/children. PNG)

We print the $Parent property in Parent and see the outermost instance of #app.

Common usage scenarios can be divided into three categories:

  • Parent-child component communication: props; parent/parent/parent/children; provide/inject; ref; ref; ref; attrs/$listeners
  • Sibling communication: EventBus; Vuex
  • Cross-level communication: EventBus; Vuex; provide/inject; attrs/attrs/attrs/listeners