Vue组件间通信详解

    众所周知,组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。那么组件之间如何通信呢?主要有,props$emit/$onvuex$parent / $children$attrs/$listenersprovide/inject,本文将分别介绍组件间的通信方式。

image.png

上图表示了组件之间所有可能的关系
A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)

第一种 props/$emit,适用于父子组件之间的通信

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app">
      <h3>组件间的通信(父子组件之间的通信props和$emit)</h3>
      <parent></parent>
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('parent', {
      template: `
        <div>
          <h2>父组件</h2>
          <p>子组件传来的数据==>{{ fromChildMsg }}</p>
          <child :msgParent="msgParent" @sendMsgToParent="childMsgHandle"></child>  
        </div>
      `,
      data(){
        return {
          msgParent:'父组件的数据',
          fromChildMsg:''
        }
      },
      methods: {
        childMsgHandle(msg){
          this.fromChildMsg = msg
        }
      }
    })
    Vue.component('child', {
      template: `
        <div>
          <h3>子组件</h3>
          <p>父组件传来的数据==>{{ msgParent }}</p>
        </div>
      `,
      props:{
        msgParent:{
          type:String
        }
      },
      data() {
        return {
          msgChild: '子组件的数据'
        }
      },
      mounted () {
        this.$emit('sendMsgToParent',this.msgChild)
      }
    })
    const app = new Vue({
      el: '#app',
      data: {}
    })
  </script>
</html>

注意:在组件间通行过程中,Vue组件遵循单向数据流原则:就是数据只能通过 props 由父组件流向子组件,而子组件并不能通过修改 props 传过来的数据修改父组件的相应状态。原因是:所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

运行结果

image.png

第二种,$attrs$listeners,适用于后代组件的通信

  • $attrs:包含了父作用域中不被prop 所识别 (且获取) 的特性绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定属性 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app">
      <h3>组件间的通信(父组件和后代之间的通信$attrs和$listeners)</h3>
      <parent />
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('parent', {
      template: `
        <div>
          <h2>父组件</h2>
          <p>子组件传来的数据==>{{ fromChildMsg }}</p>
          <p>后代件传来的数据==>{{ fromProgenyMsg }}</p>
          <child :msgParentToProgeny="msgParentToProgeny" :msgParent="msgParent" @sendMsgToParent="childMsgHandle" @progenyToParent="progenyMsgHandle"/>
        </div>
      `,
      data(){
        return {
          msgParent:'父组件的数据',
          msgParentToProgeny:'父组件数据给后代组件',
          fromChildMsg:'',
          fromProgenyMsg:''
        }
      },
      methods: {
        childMsgHandle(msg){
          this.fromChildMsg = msg
        },
        progenyMsgHandle(msg){
          this.fromProgenyMsg = msg
        }
      }
    })
    Vue.component('child', {
      template: `
        <div>
          <h3>子组件</h3>
          <p>父组件传来的数据==>{{ msgParent }}</p>
          //  后代组件中能直接触发 progenyToParent 的原因在于:子组件调用 后代组件时,使用 v-on 绑定了 $listeners 属性 
          //  通过v-bind 绑定 $attrs 属性,后代组件可以直接获取到 父组件组件中传递下来的 props(除了 子组件中 props声明的) 
          <progeny v-bind="$attrs" v-on="$listeners" />
        </div>
      `,
      props:{
        msgParent:{
          type:String
        }
      },
      data() {
        return {
          msgChild: '子组件的数据'
        }
      },
      mounted () {
        this.$emit('sendMsgToParent',this.msgChild)
      }
    })
    Vue.component('progeny',{
      template:`
        <div>
          <h3>后代组件</h3>
        </div>
      `,
      data(){
        return {
          progenyMsg:'后代组件的信息'
        }
      },
      mounted () {
        this.$emit('progenyToParent',this.progenyMsg)
      }
    })
    const app = new Vue({
      el: '#app',
      data: {}
    })
  </script>
</html>

运行结果

image.png

第三种:事件总线方式,在项目规模不大的情况下,完全可以使用中央事件总线 的方式,中央事件总线 EventBus 非常简单,就是任意组件和组件之间打交道,没有多余的业务逻辑,只需要在状态变化组件触发一个事件,然后在处理逻辑组件监听该事件就可以。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('child1', {
      template: `
        <div>
          <h3>子组件1</h3>  
          <button @click="$EventBus.$emit('child1Event',child1Msg)">bus</button>
        </div>
      `,
      data() {
        return {
          child1Msg:'组件1的信息'
        }
      }
    })
    Vue.component('child2', {
      template: `
        <div>
          <h3>子组件2</h3>  
        </div>
      `,
      mounted () {
        this.$EventBus.$on('child1Event',msg=>{
          console.log(msg)
        })
      }
    })
    // 定义事件总线
    const EventBus = new Vue()
    Vue.prototype.$EventBus = EventBus
    const app = new Vue({
      el: '#app',
      template: `
        <div>
          <h3>组件间的通信(父子组件之间的通信-事件总线)</h3>
          <child1 />
          <child2 />
        </div>
      `
    })
  </script>
</html>

image.png

第四种,provideinject,适用于根组件和后代组件通信

在父组件中通过 provider 来提供属性,然后在子组件中通过 inject 来注入变量。不论子组件有多深,只要调用了 inject 那么就可以注入在 provider 中提供的数据,而不是局限于只能从当前父组件的 prop 属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app">
      <c1 />
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('c1', {
      template: `
        <div>
          <h3>组件1</h3> 
          <p>组件1-{{ rootMsg }}</p>
          <c11 /> 
        </div>
      `,
      provide: {
        c1Msg: '组件1信息'
      },
      inject: ['rootMsg']
    })
    Vue.component('c11', {
      template: `
        <div>
          <h3>组件1-1</h3>  
          <p>组件1-1-{{ c1Msg }}</p>
          <c111 />
        </div>
      `,
      inject: ['c1Msg']
    })
    Vue.component('c111', {
      template: `
        <div>
          <h3>组件1-1-1</h3>
          <p>组件1-1-1{{ c1Msg }}</p> 
        </div>
      `,
      inject: ['c1Msg']
    })
    const app = new Vue({
      el: '#app',
      provide: {
        rootMsg: '根信息'
      }
    })
  </script>
</html>
image.png

第五种 v-model,适用于父子组件的通信

v-model用于父子组件之间的通信,体现的是v-model的实现原理:绑定value,监听input事件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app">
      <parent />
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('parent',{
      template:`
        <div>
          <h3>父组件</h3>  
          <p>父组件==>{{ message }}</p>
          <child v-model="message" />
        </div>
      `,
      data(){
        return {
          message:''
        }
      }
    })
    Vue.component('child',{
      template:`
        <div>
          <input type="text" v-model="msg" @input="changeValueHandle" /> 
        </div>
      `,
      props:{
        value:{
          type:String //v-model 会自动传递一个字段为 value 的 props 属性
        }
      },
      data(){
        return {
          msg:''
        }
      },
      methods:{
        changeValueHandle(){
          this.$emit('input',this.msg)
        }
      }
    })
    const app = new Vue({
      el: '#app'
    })
  </script>
</html>
image.png

第六种 $parent$children,适用于父子组件之间的通信

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Vue组件间的通信</title>
  </head>
  <body>
    <div id="app">
      <parent />
    </div>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    Vue.component('parent', {
      template: `
        <div>
          <h2>父组件</h2>  
          {{ childMsgFromParent }}
          <button @click="getChildMsgHandle">获取子组件信息</button>
          <child />
        </div>
      `,
      data() {
        return {
          parentMsg: '父组件的信息',
          childMsgFromParent:''
        }
      },
      methods: {
        getChildMsgHandle() {
          this.childMsgFromParent = this.$children[0].childMsg
          console.log(this.$children[0].childMsg)
        }
      }
    })
    Vue.component('child', {
      template: `
        <div>
          <h3>子组件</h3>  
          {{ parentMsgFromChild }}
          <button @click="getParentMsgHandle">获取父组件信息</button>
        </div>
      `,
      data() {
        return {
          childMsg: '子组件信息',
          parentMsgFromChild:''
        }
      },
      methods: {
        getParentMsgHandle() {
          this.parentMsgFromChild = this.$parent.parentMsg
          console.log(this.$parent.parentMsg)
        }
      }
    })
    const app = new Vue({
      el: '#app'
    })
  </script>
</html>
image.png

第七种 Vuex,全局状态管理,这里暂不做介绍。详情请看Vuex

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容