background

Vue is a single page application, and the single page application is composed of components, and each component is related to each other, so how to realize the communication between components is particularly important. Just as a person is made up of various organs, the communication between components is like blood transporting nutrients (data) to various parts. In order to keep the data flow simple and make the program easier to understand, VUE advocates single data flow. Communication between components can be divided into three types: parent-child component communication, grandchild component communication and non-associated component communication.

Parent-child component communication

Props and $emit

The parent component uses v-bind to bind data, and the child component uses $ON to receive data from the parent component. The parent component uses $ON to listen for events triggered by the child component. A brief introduction to Prop:

  • Vue data is one-way data flow, which is designed to keep the data flow simple and make the program easier to understand. Every time the parent component is updated, all the prop in the child component is refreshed to the latest value, and the prop is passed before the child component is created, so it can be used directly in Data and computed.
  • Prop should not be changed inside a child component, which would break one-way data binding and make the data flow difficult to understand. If you need it, you can receive it through the data attribute or convert it using a computed attribute.
  • If prop transfer is the basic type, then change the prop of data will be an error, if it is the reference data type if change the original data is not an error, but the assignment will be an error or change a property value, can be implemented using this father and son “two-way binding” component data, although this implementation can save code, But at the expense of unintelligible simplicity of data flow, it is best not to do so. To achieve “bidirectional binding” of data between parent and child components, you can use v-model or.sync, as described below.

v-model

V-model implements bidirectional binding of parent and child component data. It is essentially the syntax-sugar of V-bind and V-ON. Using V-Model on a component, by default, the component will be bound to a property named value and an event named input. Such as:

<test-model
v-bind:value="haorooms"
v-on:input="haorooms=$event"></test-model>
<script>
export default {
   data () {
     haorooms: ' '
   }
}
</script>
Copy the code

Is equivalent to

<test-model v-model="haorooms"></test-model>
<script>
export default {
   data () {
     haorooms: ' '
   }
}
</script>
Copy the code

Child components

<template>
<div>
  <input  
   v-bind:value="value"
   v-on="$emit('input', $event.target.value)">
</div>
</template>
<script>
  export default {
    props: ['value'],
    model: {
       prop: 'value',
       event: 'input'
    }
  }
</script>
Copy the code

.sync

The.sync modifier is similar in nature to v-model. It is also the syntactically sugar of V-bind and V-ON, for example:

<test-model
v-bind:title="doc.title"
v-on:update:title="doc.title=$event"></test-model>
<script>
export default {
   data () {
     doc: {
        title: ' '
     }
   }
}
</script>
Copy the code

Is equivalent to

<test-model v-bind:title.sync="doc.title"></test-model>
<script>
export default {
   data () {
      doc: {
        title: ' '
     }
   }
}
</script>
Copy the code

Child components

<template>
<div>
  <input v-model="value">
</div>
</template>
<script>
  export default {
     data () {
        value: ' '
     },
    watch: {
       value (val) {
          this.$emit('update:title', val)
       }
    }
  }
</script>
Copy the code

If this is the case, multiple values can be bound:

<template>
 <div id="demo">
  <test-model v-bind.sync="haorooms"></test-model>
</div>
</template>
<script>
  import testModel from './testModel'
  export default {
   data() {return{
       haorooms: {
         name: 'aaa',
         age: 18,
         value: 10
       }
     }
  },
  components: {
     testModel,
  },
  watch: {
     haorooms: {
       handler (val) {
         console.log('test', val)
       },
       deep: true
      }
  }
}
</script>
Copy the code

Child components

<template>
    <div></div>
</template>
<script>
    export default {
        data () {
            return {
                test: ' '}},mounted () {
            this.$emit('update:name', 111)
        }
    }
</script>
Copy the code

In this way, we can see that the name, age, and value properties of the Haorooms object are bidirectionally bound. When the event is triggered in the child component, we need to specify an attribute to trigger the event.

Note that v-bind with the.sync modifier cannot be used with expressions (e.g. V-bind :title.sync= “doc.title + ‘! ‘” is invalid). Instead, you can only provide the name of the property you want to bind, similar to the V-Model. Using v-bind.sync on a literal object, such as v-bind.sync= “{title: doc.title}”, does not work because there are many edge cases to consider when parsing a complex expression like this.

V-model versus.sync

Thing in common:

  • Both V-model and.sync can implement bidirectional data binding between parent and child components, essentially using the syntax sugar of V-bind and V-ON. Difference:
  • By default, V-model binds the component to a property named value and an event named input, while.sync can customize the incoming properties and events.
  • V-model can only achieve bidirectional binding of one attribute, while.sync can achieve bidirectional binding of multiple attributes.
  • The v-model needs to write porp in the child component to receive data, while.sync uses $attrs to receive data, so it doesn’t need to write prop.

$parent, $children, and ref

These three ways are obtained by direct component instance, can implement components, brother, father and son across components such as data communication, but generally is not recommended, because can increase the coupling between components, and to determine the component does not exist, if there is no may encounter an error, the several ways of simple also is not here, for example.

Across the component level

$attrs and $listeners

$attrs
background

With the improvement of project complexity, components of nested hierarchy is more and more deep, the components of communication before the use of v – bind and general prop combination, but we found that this way is only applicable to components, father and son if is grandson components will need to transfer the data of the parent to child components, child components of the data is passed to the component, sun again This requires a lot of prop writing. Is there a way to pass the parent component directly to the grandchild to make the code cleaner? This is where $attrs comes in, addressing cross-component data passing, noting that it only works for grandchild components, except for class and style data passing.

use

First we have three nested components parent A, child B, and grandson C, and then we want to pass data from A to C, using prop like this:

  <div id="app">
    A{{msg}}
    <component-b :msg="msg"></component-b>
  </div>
Copy the code
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: '100'
      },
      components: {
        'ComponentB': {
          props: ['msg'],
          template: `<div>B<component-c :msg="msg"></component-c></div>`,
          components: {
            'ComponentC': {
              props: ['msg'],
              template: '<div>C{{msg}}</div>'
            }
          }
        },
        
      }
    })
  </script>
Copy the code

$attrs = $attrs; $attrs = $attrs; $attrs = $attrs;

  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        msg: '100'
      },
      components: {
        'ComponentB': {
          template: `<div>B<component-c v-bind="$attrs"></component-c></div>`,
          components: {
            'ComponentC': {
              props: ['msg'],
              template: '<div>C{{msg}}</div>'
            }
          }
        },
        
      }
    })
  </script>
Copy the code

Conclusion: $attrs was proposed to address cross-component communication. Both prop and $attrs can be used to receive data from the parent component, but prop has a higher priority than $attrs. If both prop and $attrs have writes, the data will be received only by prop. Note that $attrs cannot receive data from class and style components.

inheritAttrs

background
<template>
  <div class="home">
    <mytest  :title="title" :message="message"></mytest>
  </div>
</template>
<script>
export default {
  name: 'home'.data () {
    return {
      title:'title1111',
      message:'message111'
    }
  },
  components:{
    'mytest': {template: ` < div > this is a h1 title {{title}} < / div > `, props: ['title'].data() {return{
          meg:'111'
        }
      },
      created:function(){
        console.log(this.$attrs)// Note here}}}} </script>Copy the code

In the above code, we only use the title property in the component, but we do not use the message property. The diagram below:

We can see that unregistered attributes in a component are rendered on the root element of the child component as normal HTML element attributes. In general, this does not affect the child component, but there are special cases such as:

<template>
    <childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data() {return {
            'name':'Joe'.'age':'30'.'sex':'male'
        }
    },
    components:{
        'childcom':{
            props:['name'.'age'],
            template:`<input type="number" style="border:1px solid blue">`,
        }
    }
}
</script>
Copy the code

We can see that the parent component’s type=”text” overwrites the value of type=”number” on the input. That’s not what I want. I want the type=”number” on the input to remain the same, but I want the value of type=”text” on the parent component. That’s where inheritAttrs come in.

<template>
    <childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data() {return {
            'name':'Joe'.'age':'30'.'sex':'male'
        }
    },
    components:{
        'childcom':{
            inheritAttrs:false,
            props:['name'.'age'],
            template:`<input type="number" style="border:1px solid blue"> `,created () {
                console.log(this.$attrs.type)
            }
        }
    }
}
</script>
Copy the code

Summary: By default, the parent component passing data to the child component that is not bound by the prop feature will be rolled back and applied to the root element of the child component as a normal HTML feature. The inheritAttrs attribute is used to remove this default behavior to avoid unpredictable effects. Note that the inheritAttrs: false option does not affect the style and class bindings.

$listeners

background

$attrs is used to pass data across components. What if you want to pass data to a parent via a grandchild component? However, if the child component doesn’t use the $emit method and wants to change the parent component’s data, we can use $Listeners.

<template>
    <div>
        <childcom :name="name" :age="age" :sex="sex" @testChangeName="changeName"></childcom>
    </div>
</template>
<script>
export default {
    'name':'test',
    props:[],
    data() {return {
            'name':'Joe'.'age':'30'.'sex':'male'
        }
    },
    components:{
        'childcom':{
            props:['name'}, template: '<div> <div> I am a child component {{name}}</div> <grandcom V-bind ="$attrs" v-on="$listeners"></grandcom>
            </div>`,
           
            components: {
                'grandcom':{template: '<div> I am grandson component -------< button@click ="grandChangeName"</button></div> ', methods:{grandChangeName(){
                           this.$emit('testChangeName'.'kkkkkk')
                        }
                    }
                }
            }
        }
    },
    methods:{
        changeName(val){
            this.name = val
        }
    }
}
</script>
Copy the code

The $Listeners are an object that contains all the listeners working on the component.

Refer to the article: www.jianshu.com/p/ce8ca875c…

Dojo.provide and inject

background

The more complex a project is, the deeper the nesting level of components is. Therefore, how the child components communicate with their ancestors is the reason why provide and Inject are proposed.

Introduction to the

This approach allows an ancestor component to inject a dependency into all of its descendants, regardless of how deep the component is nested, for as long as the upstream and downstream relationship is established. In a nutshell: Ancestor components provide variables through providers, and descendant components inject variables through inject. For example, suppose you have two components: A and B. A is the ancestor of B

// A.vue
export default {
  provide: {
    name: 'the end of the world'}}Copy the code
// B.vue
export default {
  inject: ['name'].mounted() { console.log(this.name); // tianya}}Copy the code

We can see that A variable provided in the ancestor component A can be injected and used in all of its descendants.

Therefore, if the name of A.vue above is changed, this. Name of B.vue will not change, it is still tianya.

In fact, you can think of dependency injection as part of a “prop that works on a large scale,” except for:

  • The ancestor component does not need to know which descendant components use the properties it provides
  • Descendant components don’t need to know where the injected property came from

However, di has a downside. It couples the components in your application to their current organization, making refactoring more difficult. The supplied properties are also non-reactive. This is for design reasons, as it is not good enough to use them to create a centralized scale of data as it is to use $root to do so. If the attribute you want to share is specific to your application rather than generic, or if you want to update the data provided in the ancestor component, this means you may need to switch to a true state management scheme like Vuex.

Non-associated component communication

vuex

Vuex is the state management center of VUE. The stored data is responsive, but it will not be saved. After refreshing, it will return to the initial state. It is also possible to communicate with parent, sibling, cross-level, and non-associated components. I won’t give you any examples here.

eventBus

The idea is also easy to understand: introduce the same new VUE instance in each of the two components that are communicating with each other, and then communicate by invoking the event trigger and listener of this instance respectively.

//eventBus.js
import Vue from 'vue';  
export default new Vue();
Copy the code
<! -- Component A--> <script> import Bus from'eventBus.js'; 
export default {
    methods: {  
        sayHello() {  
            Bus.$emit('sayHello'.'hello');   
        }  
    } 
}
</script>
Copy the code
<! -- component B--> <script> import Bus from'eventBus.js'; 
export default {
    created() {  
        Bus.$on('sayHello', target => { console.log(target); / / = >'hello'
        });  
    } 
}
</script>
Copy the code

$root

With the $root component, any component can get the root Vue instance of the current component tree, and data can be shared between components by maintaining data on the root instance.

// component A <script>export default {
    created() {  
        this.$root.$emit('changeTitle'.'I'm A')
    }
}
</script>
Copy the code
// component B <script>export default {
    created() {  
        this.$root.$on('changeTitle')
    },
    methods: {
      changeTitle (title)  {
         console.log(title)
      }
   }
}
</script>
Copy the code

The downside of this approach is that components A and B must exist together. Here’s another way to optimize:

//main.js root instance new Vue({el:'#app', store, router, // Root instance data attribute, maintain common data data:function () {
        return {
            author: ' '
        }
    }, 
    components: { App },
    template: '<App/>'});Copy the code
<! -- Component A--> <script>export default {
    created() {  
        this.$root.author = 'As a result'
    }
}
</script>
Copy the code
<! <div><span> </span>{$root.author }}</div>
</template>
Copy the code

In this way, although communication can be achieved, any data changes in any part of the application at any time will not leave a record of the changes. This is fatal for complicated applications and is not recommended for practical applications.

Summary: This article covers various communications between components: parent-child communication: prop and $EMIT, V-model,.sync, $parent, children, and ref

Parent/child component bidirectional binding: props and $emit, V-model,.sync

Cross-level component communication: $listeners and $Listeners, provide, and inject

Non-associated component communication: VUex, eventBus, $root

Reference article: juejin.cn/post/684490…

Juejin. Cn/post / 684490…