Vue組件間通信7種方式

前言

vue的核心就是組件的使用,組件是可復(fù)用的vue實(shí)例。如果項(xiàng)目中某一個(gè)部分需要在多個(gè)頁面中使用到,我們就可以將這部分代碼抽成一個(gè)可復(fù)用的組件。
然而,組件實(shí)例的作用域之間是相互獨(dú)立的,如果需要把組件之間的數(shù)據(jù)關(guān)聯(lián)起來,這就需要懂組件之間的通信。

一、父組件向子組件傳值(通過props)

父組件通過v-bind綁定變量,子組件通過props方式接收。

例子:在子組件Child.vue中如何獲取到父組件App.vue中的數(shù)據(jù)title: '我是父組件的數(shù)據(jù)'。

父組件

// App.vue 父組件

<template>
  <div id="app">
    <h2>父組件:</h2>
    <!-- :title 是傳到子組件的變量名,便于子組件調(diào)用 -->
    <!-- title 是父組件中的data的屬性值 -->
    <Child :title="title"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
      title: '我是父組件的數(shù)據(jù)'
    }
  },
  components: {
    Child
  }
}
</script>

<style>
</style>

子組件

// Child.vue 子組件

<template>
  <div class="child">
    <h3>子組件:{{title}}</h3>
  </div>
</template>

<script>
export default {
  name: 'Child',
  // 接收父組件的值
  props: {
    title: String
  }
}
</script>

<style scoped>
</style>

實(shí)現(xiàn)效果

image

總結(jié):這種方式只能由父組件向子組件傳遞,子組件不能更新父組件內(nèi)的data。也就是說,當(dāng)父組件的屬性發(fā)生變化時(shí),將傳遞給子組件,但不會(huì)反過來,因?yàn)閜rops是單向綁定的。

二、子組件向父組件傳值(通過$emit)

相當(dāng)于子組件調(diào)用父組件的方法。

子組件通過$emit發(fā)射一個(gè)方法,父組件通過v-on實(shí)現(xiàn)。

例子:當(dāng)我們點(diǎn)擊子組件的調(diào)用父組件中的方法按鈕時(shí),父組件中的內(nèi)容我是父組件的內(nèi)容修改成父組件的內(nèi)容被修改了,從而實(shí)現(xiàn)子組件向父組件傳值。

子組件

// Child.vue 子組件

<template>
  <div class="child">
    <h3>子組件:</h3>
    // 定義一個(gè)子組件傳值的方法 handleClick
    <input type="button" value="調(diào)用父組件中的方法" @click="handleClick">
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      title: '父組件的內(nèi)容被修改了'
    }
  },
  methods: {
    handleClick () {
      // titleChanged 自定義事件名
      // this.title 需要傳給父組件的值
      this.$emit("titleChanged", this.title)
    }
  }
}
</script>

<style scoped>
</style>

父組件

// App.vue 父組件

<template>
  <div id="app">
    <h3>父組件 -- {{title}}</h3>
    <!-- titleChanged 與子組件中自定義事件名保持一致 -->
    <!-- updateTitle 方法名,需要接收子組件傳遞過來的值 -->
    <Child @titleChanged="updateTitle"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
      title: '我是父組件的內(nèi)容'
    }
  },
  components: {
    Child
  },
  methods: {
    updateTitle (e) {
      // e 就是子組件傳遞過來的值
      this.title = e;
    }
  }
}
</script>

<style>
</style>

實(shí)現(xiàn)效果

image

三、父組件調(diào)用子組件的方法或訪問數(shù)據(jù)(通過$ref調(diào)用)

例子:在父組件App.vue中訪問子組件Child.vue中的title數(shù)據(jù)和調(diào)用childAlert方法。

子組件

// Child.vue

<template>
  <div class="child">
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      title: '我是子組件的內(nèi)容'
    }
  },
  methods: {
    childAlert () {
      window.alert('我是子組件里面的彈窗!')
    }
  }
}
</script>

<style scoped>
</style>

父組件

// App.vue

<template>
  <div id="app">
    <Child ref="childRef"/>
  </div>
</template>

<script>
import Child from './components/Child'

export default {
  name: 'App',
  data () {
    return {
    }
  },
  components: {
    Child
  },
  mounted () {
    // 訪問子組件的 title
    console.log(this.$refs.childRef.title)  // 輸出‘我是子組件的內(nèi)容’
    // 調(diào)用子組件的 childAlert 方法
    this.$refs.childRef.childAlert()
  }
}
</script>

<style>
</style>

總結(jié):ref如果直接在普通的DOM元素上使用,引用所指向的就是DOM元素,如果在子組件上使用,引用所指向的就是組件實(shí)例。

實(shí)現(xiàn)效果

image

四、非父子組件之間的通信(中央事件總線)

該方法通過一個(gè)空的Vue實(shí)例作為中央事件總線,才能使用emit獲取on的數(shù)據(jù)參數(shù),實(shí)現(xiàn)組件通信。
創(chuàng)建一個(gè)空的Vue實(shí)例文件eventBus.js,也就是一個(gè)中央事件總線。

// eventBus.js

import Vue from 'vue'
export default new Vue()

創(chuàng)建A.vue組件,引入eventBus.js文件。

// A.vue 組件

<template>
  <div class="a_com">
    <h3>A組件:</h3>
    <input type="button" value="點(diǎn)擊按鈕給B組件傳遞數(shù)據(jù)" @click="emitBCom">
  </div>
</template>

<script>
// 引入空的 vue 實(shí)例
import eventBus from '../js/eventBus'

export default {
  name: 'A',
  data () {
    return {
      msg: '我是A組件的內(nèi)容'
    }
  },
  methods: {
    emitBCom () {
      // bComHandle 自定義事件名,觸發(fā)一個(gè)可以讓B組件監(jiān)聽的方法
      // this.msg 要傳給B組件的值
      eventBus.$emit('bComHandle', this.msg)
    }
  }
}
</script>

<style scoped>
</style>

創(chuàng)建B.vue組件,引入eventBus.js文件。

// B.vue 組件

<template>
  <div class="b_com">
    <h3>B組件:</h3>
    <p>接收A組件傳遞過來的值:{{msg}}</p>
  </div>
</template>

<script>
// 引入空的 vue 實(shí)例
import eventBus from '../js/eventBus'

export default {
  name: 'B',
  data () {
    return {
      msg: '',
    }
  },
  mounted () {
    // 監(jiān)聽A組件的自定義事件
    // data 這個(gè)data就是A組件傳遞過來的值
    eventBus.$on('bComHandle', data => this.msg = data)
  }
}
</script>

<style scoped>
</style>

在App.vue中引入A.vue和B.vue兩個(gè)組件,并掛載到頁面上。

// App.vue

<template>
  <div id="app">
    <a-com />
    <b-com />
  </div>
</template>

<script>
const ACom = () => import('./components/A')
const BCom = () => import('./components/B')

export default {
  name: 'App',
  components: {
    ACom,
    BCom
  }
}
</script>

<style>
</style>

整個(gè)過程的步驟:
新建一個(gè)空的Vue實(shí)例文件eventBus.js;
在組件中引入定義的實(shí)例;
通過emit觸發(fā)一個(gè)自定義事件,并傳遞數(shù)據(jù),eventBus.emit(自定義事件名稱,要傳遞的值);
把傳遞過來的自定義事件通過on監(jiān)聽回調(diào)函數(shù),eventBus.on(自定義事件名稱, () => {})。

總結(jié):這種只用一個(gè)Vue實(shí)例來作為中央事件總線來管理非父子組件通信的方法只適用于通信需求簡(jiǎn)單一點(diǎn)的項(xiàng)目,對(duì)于更加復(fù)雜的情況,需要使用Vue提供的狀態(tài)管理模式Vuex來進(jìn)行處理。

實(shí)現(xiàn)效果

image

五、子組件調(diào)用父組件的方法或訪問數(shù)據(jù)(另外一種方法:通過$parent)

例子:在子組件Child.vue中訪問父組件App.vue中的msg數(shù)據(jù)和調(diào)用show方法。

父組件

// App.vue

<template>
  <div id="app">
    <child />
  </div>
</template>

<script>
const Child = () => import('./components/Child')

export default {
  name: 'App',
  data () {
    return {
      msg: '我是父組件中的內(nèi)容'
    }
  },
  methods: {
    show () {
      console.log('我是父組件的方法!')
    }
  },
  components: {
    Child
  }
}
</script>

<style>
</style>

子組件

// Child.vue

<template>
  <div class="child">
    <h3>我是子組件:</h3>
    <p>訪問父組件中的msg數(shù)據(jù):{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: 'Child',
  data () {
    return {
      msg: ''
    }
  },
  mounted () {
    // 訪問父組件中的 msg 數(shù)據(jù)
    this.msg = this.$parent.msg
    // 調(diào)用父組件中的 show 方法
    this.$parent.show()
  }
}
</script>

<style scoped>
</style>

總結(jié):用此方法前提得知道父組件是誰,如果項(xiàng)目中組件嵌套非常多的話,不推薦使用這個(gè)方法!

實(shí)現(xiàn)效果

image

六、跨級(jí)組件間的通信(通過provide/inject)

這對(duì)選項(xiàng)需要一起使用,以允許一個(gè)祖先組件向其所有子孫后代注入一個(gè)依賴,不論組件層次有多深,并在起上下游關(guān)系成立的時(shí)間里始終生效。
provide選項(xiàng)應(yīng)該是:一個(gè)對(duì)象或返回一個(gè)對(duì)象的函數(shù)。該對(duì)象包含可注入其子孫的屬性。
inject選項(xiàng)應(yīng)該是:一個(gè)字符串?dāng)?shù)組,或一個(gè)對(duì)象,對(duì)象的key是本地的綁定名,value是:在可用的注入內(nèi)容中搜索用的key(字符串或Symbol),或一個(gè)對(duì)象,該對(duì)象的:from屬性是在可用的注入內(nèi)容中搜索用的 key(字符串或 Symbol),default屬性是降級(jí)情況下使用的value。
假設(shè)我們有兩個(gè)組件Child.vue和Grandparent.vue,來看一下比較簡(jiǎn)單的用法:

祖先級(jí)組件

// Grandparent.vue組件

<template>
  <div class="grandparent">
    <child />
  </div>
</template>

<script>
const Child = () => import('./Child')

export default {
  name: 'Grandparent',
  provide: {
    name: 'allen'
  },
  components: {
    Child
  }
}
</script>

<style scoped>
</style>

子孫級(jí)組件

// Child.vue

<template>
  <div class="child">
    <!-- 獲取 Grandparent 組件中的 name 值 -->
    <h2>{{gp_name}}</h2>
  </div>
</template>

<script>

export default {
  name: 'Child',
  data () {
    return {
      gp_name: ''
    }
  },
  inject: ['name'],
  mounted () {
    this.gp_name = this.name
    console.log(this.name)      // 輸出 allen
  }
}
</script>

<style scoped>
</style>

我們?cè)谧嫦燃?jí)組件中設(shè)置了一個(gè)provide:name,值為allen,它的作用就是將name這個(gè)變量提供給它的所有子孫級(jí)組件。而在子孫級(jí)組件中通過inject注入了從上級(jí)組件中提供的name變量,那么在子孫級(jí)組件中,就可以直接通過this.name來訪問了。

提示:provide和inject綁定并不是可響應(yīng)的。這是刻意為之的。然而,如果你傳入了一個(gè)可監(jiān)聽的對(duì)象,那么其對(duì)象的屬性還是可響應(yīng)的。

關(guān)于這對(duì)選項(xiàng)的深入用法,大家可以去看看官方文檔哦!

七、兄弟組件之間的通信(通過Vuex)

Vuex是一個(gè)專為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。

父組件App.vue

// App.vue

<template>
  <div id="app">
    <child-a />
    <child-b />
  </div>
</template>

<script>
const ChildA = () => import('./components/ChildA')  // 導(dǎo)入 ChildA 組件
const ChildB = () => import('./components/ChildB')  // 導(dǎo)入 ChildB 組件

export default {
  name: 'App',
  components: {
    ChildA,
    ChildB
  }
}
</script>

<style>
</style>

子組件ChildA.vue

// ChildA.vue

<template>
  <div class="child-a">
    <h3>A組件:</h3>
    <input type="button" value="點(diǎn)擊按鈕讓B組件接收數(shù)據(jù)" @click="AComHandler">
    <p>B組件的數(shù)據(jù):{{bmsg}}</p>
  </div>
</template>

<script>

export default {
  name: 'ChildA',
  data () {
    return {
      amsg: '我是A組件的內(nèi)容'
    }
  },
  computed: {
    bmsg () {
      return this.$store.state.BComMsg
    }
  },
  methods: {
    AComHandler () {
      this.$store.commit('transferAComMsg', {
        AComMsg: this.amsg
      })
    }
  }
}
</script>

<style scoped>
</style>

子組件ChildB.vue

// ChildB.vue

<template>
  <div class="child-b">
    <h3>B組件:</h3>
    <input type="button" value="點(diǎn)擊按鈕讓A組件接收到數(shù)據(jù)" @click="AComHandler">
    <p>A組件的數(shù)據(jù):{{amsg}}</p>
  </div>
</template>

<script>

export default {
  name: 'ChildB',
  data () {
    return {
      bmsg: '我是B組件的內(nèi)容'
    }
  },
  computed: {
    amsg () {
      return this.$store.state.AComMsg
    }
  },
  methods: {
    AComHandler () {
      this.$store.commit('transferBComMsg', {
        BComMsg: this.bmsg
      })
    }
  }
}
</script>

<style scoped>
</style>

引入vuex模塊

> npm install vuex --save

在src文件夾下創(chuàng)建store文件夾,并創(chuàng)建index.js文件

// index.js 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    // 初始化A組件和B組件的數(shù)據(jù)
    AComMsg: '',
    BComMsg: ''
  },
  mutations: {
    // 將A組件數(shù)據(jù)存放到state中
    transferAComMsg (state, payload) {
      state.AComMsg = payload.AComMsg
    },
    // 將B組件數(shù)據(jù)存放到state中
    transferBComMsg (state, payload) {
      state.BComMsg = payload.BComMsg
    }
  }
})

export default store

在main.js導(dǎo)入

// main.js

import Vue from 'vue'
import App from './App'

import store from './store/index'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 vue的核心就是組件的使用,組件是可復(fù)用的vue實(shí)例。如果項(xiàng)目中某一個(gè)部分需要在多個(gè)頁面中使用到,我們就可以...
    Oct13_JJP閱讀 860評(píng)論 0 20
  • 摘要: 總有一款合適的通信方式。 作者:浪里行舟 Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 前言 組件是 v...
    Fundebug閱讀 15,646評(píng)論 3 57
  • 前言 組件是 vue.js最強(qiáng)大的功能之一,而組件實(shí)例的作用域是相互獨(dú)立的,這就意味著不同組件之間的數(shù)據(jù)無法相互引...
    用技術(shù)改變世界閱讀 2,305評(píng)論 1 3
  • 組件系統(tǒng) 組件通訊是vue.js的核心之一,不管是在項(xiàng)目中,還是面試中,都是必考的知識(shí)點(diǎn)之一。組件通訊通常涉及到父...
    大臉貓的前端之路閱讀 917評(píng)論 0 3
  • 隨著五下的時(shí)光,迎來了炎熱的暑假,但是說是“假”,其實(shí)也并不全是,我打算來借這個(gè)暑假,給我的大腦補(bǔ)充“營(yíng)養(yǎng)”,...
    仙baby閱讀 397評(píng)論 0 1

友情鏈接更多精彩內(nèi)容