preface
Component development as one of the core ideas of VUE framework, when we use VUE framework to do front-end development, we have to deal with it inevitably. In real development, the scope between vUE component instances is independent of each other, but components need to communicate with each other, share state, and so on. Therefore, it is very important to master the communication between VUE components. Here is a summary of N kinds of communication of VUE to review and consolidate knowledge.
Relationships between components
When it comes to communication between components, it is important to understand what relationships exist between components. (Stolen online photo)
- Parent: COMPONENT A -> component B
- Non-parent: next generation A component -> C component; Sibling B component -> C component, etc
How components communicate with each other
After the relationship between components is introduced, let’s take a look at the communication modes available in vUE
- V-bind and props (pass values via binding properties)
- V-on and $emit (send values by triggering events)
- $ref, $parent, $children
- Provide and inject (use dependency injection for value transmission)
- $listeners and $attrs
- EventBus (using the EventBus to pass values)
- Vuex (Use the vuex plug-in to transmit values)
- Use local storage and vue-router
Detailed description of communication mode between components
Communication between parent and child components
The following examples are tested using the VUE project created by VUE-CLI3Copy the code
// app.vue <template> <div id="app"> <parent></parent> </div> </template> <script> import parent from './components/parent'; export default { name: 'App', components: { Parent } }; </script>Copy the code
Parent component passes value to child component (V-bind and props)
// Parent <template> <div> <h1> parent </h1> // On the imported child, bind a property Pmsg with v-bind, Value: MSG <children :Pmsg=" MSG "></children> </div> </template> <script> import children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'Message from parent component'}; }}; </script>Copy the code
// children child <template> <div class="children"> <h2> child </h2> <p>{{Pmsg}}</p> </div> </template> <script> export Default {name: 'children', // accept properties bound in the parent component via props, and then use them directly internally. // Props can also be props: ['Pmsg'], // props: {// Pmsg: {// type: String // Received type, // default: any, // required: Boolean, // validator: Function, // } // }, mounted() { console.log(this.Pmsg); }}; </script>Copy the code
On the page, the child component has received the value passed in by the parent component
Child component passes value to parent component (V-ON and $emit)
// Parent <template> <div> <h1> parent </h1> <p>{{MSG}}</p> Listen for the sub component \$emit('receiveData'), The parent component triggers parnetReceiveData <children @receiveData="parnetReceiveData"></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'parent component'}; }, methods: { parnetReceiveData(data) { console.log(data); this.msg = data; }}}; </script>Copy the code
// children <template> <div class="children"> <h2> </h2> <p @click="emitData">{{MSG}}</p> </div> </template> <script> export default {name: 'children', data() {return {MSG: 'child value '}; }, methods: {emitData() {// Trigger the receiveData event on the parent component's label, // trigger the parent component's event, use parameter passing, achieve the effect of passing. this.$emit('receiveData', this.msg); }}}; </script>Copy the code
The parent component successfully receives the value from the child component after clicking the $emit event
$parnet 和 $children、$ref
// parent <template> <div> <h1> <p @click="emitData">{{MSG}}</p> <children></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'parent component'}; }, methods: {emitData() {console.log('praent's child ', this.$children); This.$children[0]. MSG = $children[0]; }}}; </script>Copy the code
// children <template> <div class="children"> <h2> </h2> <p @click="emitData">{{MSG}}</p> </div> </template> <script> export default {name: 'children', data() {return {MSG: 'child value '}; }, methods: {emitData() {console.log('children's parent ', this.$parent); This.$parent. MSG = 'dom '; }}}; </script>Copy the code
$children = $children = $children = $children = $children = $children = $children = $children = $children = $children
Using the ref
// parent <template> <div> <h1 ref="h1"> parent </h1> <p @click="changeData">{{MSG}}</p> <children ref="children"></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'parent component'}; }, methods: {changeData() {console.log(this. refs); This.$refs['children']. MSG = 'ref '; }}}; </script>Copy the code
// Children <template> <div class="children"> // Use ref to identify h2 tags in children <h2 ref="h2"> </h2> <p @click="showRoot">{{ MSG}}</p> </div> </template> <script> export default {name: 'children', data() {return {MSG: 'children'; }, methods: {showRoot() {// print $root console.log(this.$root); }}}; </script>Copy the code
Click the parent component on the page, and the console can see the refs object of the parent component, which respectively saves the two VNodes identified by ref in the parent component. By searching down, it can be seen that the REF identified in the child component also exists in the vNode of the child component
Small development
After introducing the first three, the value transfer between parent and child components is basically introduced. Now that you know both props and $emit, you can extend the V-model API and the.sync modifier
- The V-model is a syntax-sugar that uses props and $emit to implement two-way data binding in the input
// Parnet parent component
<template>
<div>
<h1>The parent component</h1>
<p>{{ msg }}</p>// Use v-model to achieve two-way data binding effect<input type="text" v-model="msg">
</div>
</template>
<script>
export default {
name: 'praent',
data() {
return {
msg: 'Original value of parent component'}; }};</script>
Copy the code
When you enter or modify values in the input box on the page, the P tag is also modified
// parent parent component
<template>
<div>
<h1>The parent component</h1>
<p>{{ msg }}</p>
<input
type="text"
:value="msg"/ / to monitorinputGets the value of the event and assigns it tomsg
@input="msg = $event.target.value"
>
</div>
</template>
<script>
export default {
name: 'praent',
data() {
return {
msg: 'Original value of parent component'}; }};</script>
Copy the code
You can get the same effect, so if you’re interested, you can try it
However, in VUE, the V-Model internally uses different properties for different input elements and throws different events:
- Text and Textarea elements use value property and input events;
- Checkbox and radio use checked Property and change events;
- The SELECT field takes value as prop and change as an event.
These can be found in vUE’s official help documentation, as well as details on the use of v-model custom events for custom components
- Sync modifier Let’s look at how the.sync modifier is used
< / / parent the parent component template > < div > < h1 > parent component < / h1 > < p > {{MSG}} < / p > / / in the transmission of data, <children :msg.sync=" MSG "></children> </div> </template> <script> import children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'parent component'}; }}; </script>Copy the code
// children <template> <div class="children"> <h2> </h2> <p @click="syncData">{{MSG}}</p> </div> </template> <script> export default { name: 'children', props: ['msg'], methods: $emit('update: MSG ', 'sync ') {// this.$emit('update: MSG ',' sync '); }}}; </script>Copy the code
Launch the page, and click the value of the child component in the page
<template> <div> <h1> parent </h1> <p>{{MSG}}</p> // the @update: MSG is changed to. Sync <children :msg="msg" @update:msg="msg = $event" ></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'parent component'}; }}; </script>Copy the code
Non-parent components pass values
Dojo.provide and inject
Provide and inject are applicable to transfer values between generations, grandparent or deeper levels.
Official note: 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, for as long as the upstream and downstream relationship is established.
Also pay attention to this tip on the official website:
Tip: Provide and Inject binding are not responsive. This is intentional. However, if you pass in a listening object, the object’s property is still responsive.
That is, Vue does not respond to variables in provide. But if I pass in a variable that’s already been treated in a reactive way the inject that accepts the variable will also be reactive. Here’s the code to illustrate the use of the two apis
// provide
// 1. We can use the factory function to return an object
provide: function () {
return {
msg: this.msg
}
}
// 2. Can be an object directly
provide: {
msg: 'Incoming value'
}
Copy the code
// inject
// 1. Receive an array
inject: ['msg']
2. Receive an object
inject: {
msg: { // The MSG value can also be a string
from: 'msg' // Inject the key into the content
default: ' ' // The default value can also be a factory function}}Copy the code
Non-responsive data transfer code demonstration
// parent <template> <div> <h1 ref="h1"> parent </h1> <p @click="changeData">{{MSG}}</p> <children ref="children"></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'from father'}; }, methods: {changeData() {this.msg = 'parent component changed the MSG value passed down '; } }, provide() { return { msg: this.msg }; }}; </script>Copy the code
// Children <template> <div class="children"> <h2 ref="h2"> child </h2> <p>{{MSG}}</p> <grandson></ div> </template> <script> import Grandson from './grandson'; export default { name: 'children', components: { Grandson }, inject: ['msg'] }; </script>Copy the code
Launch the page, click on the parent component p TAB, the parent component has changed its value, and the value passed down has not changed
If the value passed in is reactive
<template> <div> <h1 ref="h1"> parent component </h1> <p @click="changeData">{{msg.value}}</p> <children ref="children"></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: {value: 'Value from parent'}}; }, methods: {changeData() {this.msg.value = 'Parent component changed the MSG value passed down '; } }, provide() { return { msg: this.msg }; }}; </script>Copy the code
// Child <template> <div class="children"> <h2 ref="h2"> child </h2> <p>{{msg.value}}</p> <grandson></grandson> </div> </template> <script> import Grandson from './grandson'; export default { name: 'children', components: { Grandson }, inject: ['msg'] }; </script>Copy the code
// Grandson <template> <div class="grandson"> < H3 >{{msg.value}}</p> </div> </template> <script> export default { name: 'grandson' inject: ['msg'] }; </script>Copy the code
Start the page and click on the P TAB of the parent component. Now not only the value of the parent component has been updated, but also the values of the child and grandchild components have changed
Modify the grandson component slightly, define a value to store the reactive value of inject, and then click the event to modify the value,
Grandson ="changeMsg">{grandsonmsg. value}}</p> </div> </template> <script> export default { name: 'grandson', data() { return { grandsonMsg: '' } }, mounted(){ this.grandsonMsg = this.msg }, inject: [' MSG '], methods:{changeMsg(){this.grandsonmsg.value = 'MSG'}}}; </script>Copy the code
As you can see, the descendant component receives this reactive value and modifies it in this way, also modifying the value provided by the parent component
Provide and Inject exist in a large number of high-level components, such as forms and buttons in Element-UI, which use these two apis
$attrs and $listeners
Description of $attrs in the official documentation
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 received properties do not include class, style, or properties that have been retrieved by prop. Look directly at the code while paying attention to the inheritAttrs configuration item in the component.
The official documentation describes the inheritAttrs configuration items
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
// parent <template> <div> <h1 ref="h1"> </h1> <p>{{MSG}}</p> <children :msgToChildren="msgToChildren" :msgToGrandson="msgToGrandson" ></children> </div> </template> <script> import Children from './children'; export default { name: 'praent', components: { Children }, data() { return { msgToChildren: 'Father's value passed to child's value ', msgToGrandson:' Father's value passed to grandson's value '; }}; </script>Copy the code
// Child <template> <div class="children"> <h2 ref="h2"> child </h2> <p>{{this.msgToChildren}}</p> <grandson v-bind="$attrs"></grandson> </div> </template> <script> import Grandson from './grandson'; export default { name: 'children', // // inheritAttrs: false, components: { Grandson }, props: ['msgToChildren'] }; </script>Copy the code
{{msgToGrandson}}</p> </div> </template> <script> export default { name: 'grandson', props: ['msgToGrandson'] }; </script>Copy the code
Grandson can get the values passed by the parent, and notice the properties on the child’s TAB. MsgToGrandson still exists
Set the inheritAttrs configuration item in the child component to false
// Children <template> <div class="children"> <h2 ref="h2"> child </h2> <p>{{this.msgToChildren}}</p> <grandson v-bind="$attrs"></grandson> </div> </template> <script> import Grandson from './grandson'; export default { name: 'children', inheritAttrs: false, components: { Grandson }, props: ['msgToChildren'] }; </script>Copy the code
When you look at the page again, the msgToGrandson property on the child component is discarded
Note how $listeners use $listeners
Description of $listrners in official documentation
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.
Note that $Listeners can listen to all the events and see the code implementation directly
<template> <div> <h1 ref="h1"> parent component </h1> < p@click ="changrMsg">{{MSG}}</p> <children :msgToChildren="msgToChildren" :msgToGrandson="msgToGrandson" @changeFromChildren="changeFromChildren" @changeFromGrandson="changeFromGrandson" ></children> </div> </template> <script> import Children from './children'; Export default {name: 'praent', components: {Children}, data() {return {MSG: 'reset parent ', msgToChildren: 'Father's value passed to child's value ', msgToGrandson:' Father's value passed to grandson's value '; }, methods: {changrMsg() {console.log(' reset MSG '); This. msgToChildren = 'Father's value passes to children's value '; This.msgtograndson = 'father's value passed grandson's value '; }, changeFromChildren() {console.log(' triggers the parent changeFromChildren event '); This.msgtochildren = 'Child component changed MSG value '; }, changeFromGrandson() {console.log(' trigger parent changeFromGrandson event '); This.msgtostall = 'grandson changed MSG value '; } }, provide() { return { msg: this.msg }; }}; </script>Copy the code
<template> <div class="children"> <h2 ref="h2"> <p @click="changeMsg">{{this.msgToChildren}}</p> <grandson v-bind="$attrs" v-on="$listeners" ></grandson> </div> </template> <script> import Grandson from './grandson'; export default { name: 'children', inheritAttrs: false, components: { Grandson }, props: ['msgToChildren'], methods: { changeMsg() { this.$emit('changeFromChildren'); }}}; </script>Copy the code
<template> <div class="grandson"> <h3> < p@click ="changeMsg">{{msgToGrandson}}</p> <p @click="changeChildrenMsg"> </p> </div> </template> <script> export default {name: 'grandson', props: ['msgToGrandson'], methods: { changeMsg() { this.$emit('changeFromGrandson'); }, changeChildrenMsg() { this.$emit('changeFromChildren'); }}}; </script>Copy the code
A small summary
Note that $listeners and $listeners are typically used to communicate between parent and listeners, but if the level is too deep and you need to set $attrs and $Listeners to each layer, you may need to consider using other methods. Provide and inject are used for grandparents to transfer values to their descendants, and in certain scenarios, they can be used to realize state sharing of sibling components. This is basically all that VUE provides for component communication. Then we can see that VUE does not provide a direct way to communicate between sibling components. What can we do with sibling communication?
Sibling communication
Since VUE does not provide a direct means of communication, we generally need to use other means of communication.
EventBus indicates the EventBus
The principle of the event bus is to use the publish-subscribe model to create A mediation, where component A informs the mediation of the value to be passed or the event to be triggered, and the mediation informs component B. Start by simply implementing a publish subscription
class Eventbus {
constructor() {
this.list = {};
}
$on(type, fn) {
if (this.list[type]) {
this.list[type].push(fn);
} else {
this.list[type] = [];
this.list[type].push(fn);
}
}
$emit(type) {
if (this.list[type]) {
this.list[type].forEach(fn= >{ fn(); }); }}}const eventBus = new Eventbus();
export default eventBus;
Copy the code
Add a listening event when the parent component, the first component, finishes loading
< / / parent the parent component template > < div > < h1 ref = "h1" > parent component < / h1 > < p > {{MSG}} < / p > < first > < / first > < second > < / second > < / div > < / template > <script> import First from './first'; import Second from './second'; import eventBus from './eventBus'; Export default {name: 'praent', components: {First, Second}, data() {return {MSG: 'parent'}; }, mounted () {eventBus $on (" aaa ", () = > {the console. The log (' parent component of MSG is amended); This. MSG = 'parent component's MSG has been modified '; }); }}; </script>Copy the code
<div class="first"> <h3> </h3> <p>{{MSG}}</p> </div> </template> <script> import eventBus from './eventBus'; Export default {name: 'first', data() {return {MSG: 'first sibling '}; }, mounted() {eventBus.$on('aaa', () => {console.log(' MSG of first component is modified '); This. MSG = 'the MSG of the first sibling component has been modified '; }); }}; </script>Copy the code
Add an event to the second component that triggers events defined in eventBus
<template> <div class="second"> <h3> < p@click ="emitEventBus"> </p> </div> </template> <script> import eventBus from './eventBus'; Export default {name: 'second', methods: {emitEventBus() {console.log(' second sibling event '); eventBus.$emit('aaa'); }}}; </script>Copy the code
When we click on the second component, we can see that if $ON listens for the event in eventBus, it can emit the event anywhere through eventBus $emit.
Of course, in the actual development, the VUE instance itself has realized the event monitoring and triggering, we can use the VUE instance to do the attribute and event monitoring, we do not need to implement a publish subscription
We can modify the eventBus file
import Vue from 'vue'
const eventBus = new Vue()
export default eventBus;
Copy the code
You can test it yourself, and it can also be used
We can also mount events to the prototype of the vue root instance in main.js so that we don’t have to refer to eventBus every time we need to use it
// main.js
import Vue from 'vue'
import App from './App.vue'
import eventBus from './components/eventBus';
Vue.prototypt.$eventBus = eventBus
Vue.config.productionTip = false
new Vue({
render: h= > h(App),
}).$mount('#app')
Copy the code
Adding listening and emit events is a simple code change. It’s a lot easier to use
this.$eventBus.$on('aaa', () = > {console.log('The MSG of the first sibling component has been modified');
this.msg = 'The MSG of the first sibling component has been modified';
});
this.$eventBus.$emit('aaa');
Copy the code
vuex
The last vueX is left. You can directly read the official document vueX to check its use.
Write a separate article later when you have time.