Vue's parent-child component communication (ten types)

Interviewer: What are the ways to communicate between parent and child components in Vue?

Think about it for a minute.

It is undeniable that both large and small factories have already used the Vue.js framework. It is simple and easy to use, not to mention, the tutorial is detailed, the community is active, and there are many third-party kits. It is really an essential skill for front-end developers. And in the interview, various questions about Vue are often asked, and most of the interviewers will ask the above questions.

Recently, I have been doing optimizations at the code level of the Vue project. To be honest, optimizing other people's code is really a painful thing. If the function implementation is not mentioned, I can write another article on the code specification. There is really no standard and no radius. It is too important to regulate this thing! It’s a bit of a joke, back to the topic, cough cough, let’s talk about my understanding of the above interview questions, the writing is limited, and there is something wrong. Welcome to leave a comment at the end of the article.

Overview

Several communication methods are nothing more than the following:

  • Prop(Commonly used)

  • $emit (Most used for component packaging)

  • .syncSyntactic sugar (less)

  • $attrs And  $listeners (more used for component packaging)

  • provide And  inject (high-end components/component libraries are used more)

  • Other communication

Detail

Let’s introduce them one by one, please go around.

1. Prop
British pronunciation: [prɒp]. This is very much used in our daily development. Simply put, we can pass data to subcomponents through Prop. To use a vivid metaphor, the data transfer between the parent and child components is equivalent to a top-down sewer pipe, which can only flow from top to bottom, not upstream. This is exactly the one-way data flow of Vue's design philosophy. Prop is just an interface between the pipeline and the pipeline, so that water (data) can flow down. Having said that, look at the code:

<div id="app">
 
  <child :content="message"></child>
 
</div>
 
// Js
 
let Child = Vue.extend({
    
    
 
  template: '<h2>{
    
    { content }}</h2>',
 
  props: {
    
    
 
    content: {
    
    
 
      type: String,
 
      default: () => {
    
     return 'from child' }
 
    }
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  data: {
    
    
 
    message: 'from parent'
 
  },
 
  components: {
    
    
 
    Child
 
  }
 
})

Browser output:

from parent

 
  
  

2. The
British pronunciation of $emit : [iˈmɪt]. The official statement is to trigger an event on the current instance. Additional parameters will be passed to the listener callback . According to my understanding, I don’t know if I can explain it to you. Let’s take a brief look at the code:

<div id="app">
 
  <my-button @greet="sayHi"></my-button>
 
</div>
 
let MyButton = Vue.extend({
    
    
 
  template: '<button @click="triggerClick">click</button>',
 
  data () {
    
    
 
    return {
    
    
 
      greeting: 'vue.js!'
 
    }
 
  },
 
  methods: {
    
    
 
    triggerClick () {
    
    
 
      this.$emit('greet', this.greeting)
 
    }
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  components: {
    
    
 
    MyButton
 
  },
 
  methods: {
    
    
 
    sayHi (val) {
    
    
 
      alert('Hi, ' + val) // 'Hi, vue.js!'
 
    }
 
  }
 
})

The general logic is Jianger's: when I click the button on the page, I trigger the MyButtonlistening event on the component greetand pass the parameters to the callback function sayHi. To put it bluntly, before we Emit (distribute) an event from the child component, it internally has On (listened) the event and its listener callback in the event queue in advance. In fact, it is equivalent to the following writing:

vm.$on('greet', function sayHi (val) {
    
    
 
  console.log('Hi, ' + val)
 
})
 
vm.$emit('greet', 'vue.js')
 
// => "Hi, vue.js"

3. .sync modifier
This guy used to be a two-way binding function in [email protected], that is, the child component can modify the value in the parent component. Because it violated the design concept of one-way data flow, it was killed in [email protected]. But in [email protected]+ and above versions, this .sync modifier has been reintroduced. But this time it only exists as a compile-time syntactic sugar. It will be expanded into a v-on listener that automatically updates the properties of the parent component. To put it bluntly, let us manually update the value in the parent component to make the source of the data change more obvious. Here is a paragraph from the official introduction:

In some cases, we may need to "two-way binding" a prop. Unfortunately, true two-way binding will bring maintenance problems, because the child component can modify the parent component, and there is no obvious source of change in the parent component and the child component.

Since it is a syntactic sugar, it must be a shorthand form of a certain way of writing. Which way of writing? Look at the code:

So we can use .syncsyntactic sugar to abbreviate as follows:

<text-document
 
  v-bind:title="doc.title"
 
  v-on:update:title="doc.title = $event">
 
</text-document>
<text-document v-bind:title.sync="doc.title"></text-document>

So much nonsense, how to achieve "two-way binding"? Let's enter the advertisement, it will be more exciting after the advertisement! ... Okay, welcome back. Suppose we want to achieve such an effect: changing the value in the text box of the child component also changes the value in the parent component. How to do it? Think about it first. Look at the code first:

<div id="app">
 
  <login :name.sync="userName"></login> {
    
    {
    
     userName }}
 
</div>
 
let Login = Vue.extend({
    
    
 
  template: `
 
    <div class="input-group">
 
      <label>姓名:</label>
 
      <input v-model="text">
 
    </div>
 
  `,
 
  props: ['name'],
 
  data () {
    
    
 
    return {
    
    
 
      text: ''
 
    }
 
  },
 
  watch: {
    
    
 
    text (newVal) {
    
    
 
      this.$emit('update:name', newVal)
 
    }
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  data: {
    
    
 
    userName: ''
 
  },
 
  components: {
    
    
 
    Login
 
  }
 
})

Below is the key point, there is this sentence in the code:

this.$emit('update:name', newVal)

 
  
  

The official syntax is: update:myPropNamewhich myPropNamerepresents the prop value to be updated. Of course, if you use the above .$emit without .sync sugar, the same effect can be achieved. That's it!

4. $attrsand$listeners

  • $attrsThe explanation on the official website is as follows:

Contains property bindings ( classand styleexceptions) that are not recognized (and acquired) as props in the parent scope . When a component does not declare any prop, it will contain all parent scope bindings ( classand styleexceptions), and can v-bind="$attrs"pass in internal components-very useful when creating high-level components.

  • $listenersThe explanation on the official website is as follows:

Contains event listeners in the parent scope (without .nativedecorators) v-on. It can be v-on="$listeners"passed in internal components-very useful when creating higher-level components.

I think $attrsand $listenersattributes are like two storage boxes, one is responsible for storing attributes and the other is responsible for storing events, both of which store data in the form of objects. See the following code explanation:

<div id="app">
 
  <child
 
    :foo="foo"
 
    :bar="bar"
 
    @one.native="triggerOne"
 
    @two="triggerTwo">
 
  </child>
 
</div>

As you can see from Html, there are two attributes and two methods. The difference is that the attribute is a propdeclaration and the event is a .nativemodifier.

let Child = Vue.extend({
    
    
 
  template: '<h2>{
    
    { foo }}</h2>',
 
  props: ['foo'],
 
  created () {
    
    
 
    console.log(this.$attrs, this.$listeners)
 
    // -> {
    
    bar: "parent bar"}
 
    // -> {
    
    two: fn}
 
 
 
    // 这里我们访问父组件中的 `triggerTwo` 方法
 
    this.$listeners.two()
 
    // -> 'two'
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  data: {
    
    
 
    foo: 'parent foo',
 
    bar: 'parent bar'
 
  },
 
  components: {
    
    
 
    Child
 
  },
 
  methods: {
    
    
 
    triggerOne () {
    
    
 
      alert('one')
 
    },
 
    triggerTwo () {
    
    
 
      alert('two')
 
    }
 
  }
 
})
可以看到,我们可以通过$attrs$listeners进行数据传递,在需要的地方进行调用和处理,还是很方便的。当然,我们还可以通过v-on="$listeners"一级级的往下传递,子子孙孙无穷尽也!

一个插曲!

当我们在组件上赋予了一个非Prop 声明时,编译之后的代码会把这些个属性都当成原始属性对待,添加到 html 原生标签上,看上面的代码编译之后的样子:

<h2 bar="parent bar">parent foo</h2>
这样会很难看,同时也爆了某些东西。如何去掉?这正是 inheritAttrs 属性的用武之地!给组件加上这个属性就行了,一般是配合$attrs使用。看代码:

// 源码
 
let Child = Vue.extend({
    
    
 
  ...
 
  inheritAttrs: false, // 默认是 true
 
  ...
 
})

It can be seen that it is very convenient for us to transfer data through $attrsand $listenersto call and process where needed. Of course, we can also pass v-on="$listeners"down through the first level, endless descendants!

An episode!

When we assign a non-Prop declaration on the component, the compiled code will treat these attributes as original attributes and add them to the html native tags. See how the above code looks after being compiled:

<h2 bar="parent bar">parent foo</h2>

 
  
  

This will be ugly, and it will explode something. How to remove it? This is where the inheritAttrs attribute comes in! Just add this attribute to the component, generally used in conjunction $attrs. Look at the code:


 
  
  
  1. // Source code
  2. let Child = Vue.extend ({
  3. ...
  4. inheritAttrs: false , // Default is true
  5. ...
  6. })

Compile again:

<h2>parent foo</h2>

 
  
  

5. provide/inject
They are very mysterious to CP. Take a look at the official description of provide / inject:

provideAnd injectmainly provide use cases for high-end plug-in/component libraries. It is not recommended for direct use in application code. And this pair of options needs to be used together to allow an ancestor component to inject a dependency to 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.

A bit ignorant after reading the description! One sentence summary is: When you were young, your dad would save everything for you. When you grow up, you should marry a wife. You want a house or a car for you, as long as he has everything he can. Here is the code explanation of this sentence:

<div id="app">
 
  <son></son>
 
</div>
 
let Son = Vue.extend({
    
    
 
  template: '<h2>son</h2>',
 
  inject: {
    
    
 
    house: {
    
    
 
      default: '没房'
 
    },
 
    car: {
    
    
 
      default: '没车'
 
    },
 
    money: {
    
    
 
      // 长大工作了虽然有点钱
 
      // 仅供生活费,需要向父母要
 
      default: '¥4500'
 
    }
 
  },
 
  created () {
    
    
 
    console.log(this.house, this.car, this.money)
 
    // -> '房子', '车子', '¥10000'
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  provide: {
    
    
 
    house: '房子',
 
    car: '车子',
 
    money: '¥10000'
 
  },
 
  components: {
    
    
 
    Son
 
  }
 
})

6. Other communication methods
In addition to the above five methods, there are actually:

  • EventBus

The idea is to declare a global Vue instance variable EventBusand store all communication data and event monitoring in this variable. In this way, data sharing between components is achieved, similar to Vuex. But this method is only suitable for very small projects, and Vuex is recommended for complex projects. Here is the simple code to implement EventBus:

<div id="app">
 
  <child></child>
 
</div>
 
// 全局变量
 
let EventBus = new Vue()
 
 
 
// 子组件
 
let Child = Vue.extend({
    
    
 
  template: '<h2>child</h2>',
 
  created () {
    
    
 
    console.log(EventBus.message)
 
    // -> 'hello'
 
    EventBus.$emit('received', 'from child')
 
  }
 
})
 
 
 
new Vue({
    
    
 
  el: '#app',
 
  components: {
    
    
 
    Child
 
  },
 
  created () {
    
    
 
    // 变量保存
 
    EventBus.message = 'hello'
 
    // 事件监听
 
    EventBus.$on('received', function (val) {
    
    
 
      console.log('received: '+ val)
 
      // -> 'received: from child'
 
    })
 
  }
 
})
  • Vuex

Officially recommended, Vuex is a state management mode developed specifically for Vue.js applications.

  • $parent

The parent instance, if the current instance has one. The interaction between data can also be carried out by accessing the parent instance, but in rare cases, the data in the parent component will be directly modified.

  • $root

The root Vue instance of the current component tree. If the current instance does not have a parent instance, this instance will be itself. The interaction between data can also be carried out by accessing the root component, but in very rare cases the data in the parent component will be directly modified.

  • broadcast / dispatch

They are methods in [email protected], which are event broadcasting and event dispatching. Although [email protected] has been deleted, these two methods can be simulated. Can learn from Element to achieve. Sometimes it is very useful, such as when we are developing tree components.

to sum up

After so much long-winded, I hope to see the students more or less gain. Please leave a comment if something is wrong, I am very grateful. There are actually many types of communication between parent and child components, depending on what circumstances you use. Different scenarios are treated differently. The premise is that you have to know it! There is still a long way to go through the Great God. As long as you look at the community every day, look at the documentation, write demos, and make a little progress every day, you will always gain.

 

Interviewer: What are the ways to communicate between parent and child components in Vue?

Guess you like

Origin blog.csdn.net/weixin_46034375/article/details/108541546