歡迎你們關注微信公衆號vue
vue是數據驅動視圖更新的框架, 因此對於vue來講組件間的數據通訊很是重要,那麼組件之間如何進行數據通訊的呢?程序員
首先咱們須要知道在vue中組件之間存在什麼樣的關係, 才更容易理解他們的通訊方式, 就好像過年回家,坐着一屋子的陌生人,相互之間怎麼稱呼,這時就須要先知道本身和他們之間是什麼樣的關係。vuex
vue組件中關係說明:api
如上圖所示, A與B、A與C、B與D、C與E組件之間是父子關係; B與C之間是兄弟關係;A與D、A與E之間是隔代關係; D與E是堂兄關係(非直系親屬)數組
針對以上關係咱們歸類爲:微信
- 父子組件之間通訊
- 非父子組件之間通訊(兄弟組件、隔代關係組件等)
本文會介紹組件間通訊的8種方式以下圖目錄所示:並介紹在不一樣的場景下如何選擇有效方式實現的組件間通訊方式,但願能夠幫助小夥伴們更好理解組件間的通訊。session
1、props / $emitapp
父組件經過props的方式向子組件傳遞數據,而經過$emit 子組件能夠向父組件通訊。框架
1. 父組件向子組件傳值異步
下面經過一個例子說明父組件如何向子組件傳遞數據:在子組件article.vue中如何獲取父組件section.vue中的數據articles:['紅樓夢', '西遊記','三國演義']
- // section父組件
- <template>
- <div class="section">
- <com-article :articles="articleList"></com-article>
- </div>
- </template>
- <script>
- import comArticle from './test/article.vue'
- export default {
- name: 'HelloWorld',
- components: { comArticle },
- data() {
- return {
- articleList: ['紅樓夢', '西遊記', '三國演義']
- }
- }
- }
- </script>
- // 子組件 article.vue
- <template>
- <div>
- <span v-for="(item, index) in articles" :key="index">{{item}}</span>
- </div>
- </template>
- <script>
- export default {
- props: ['articles']
- }
- </script>
總結: prop 只能夠從上一級組件傳遞到下一級組件(父子組件),即所謂的單向數據流。並且 prop 只讀,不可被修改,全部修改都會失效並警告。
2. 子組件向父組件傳值
對於$emit 我本身的理解是這樣的: $emit綁定一個自定義事件, 當這個語句被執行時, 就會將參數arg傳遞給父組件,父組件經過v-on監聽並接收參數。 經過一個例子,說明子組件如何向父組件傳遞數據。
在上個例子的基礎上, 點擊頁面渲染出來的ariticle的item, 父組件中顯示在數組中的下標
- // 父組件中
- <template>
- <div class="section">
- <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
- <p>{{currentIndex}}</p>
- </div>
- </template>
- <script>
- import comArticle from './test/article.vue'
- export default {
- name: 'HelloWorld',
- components: { comArticle },
- data() {
- return {
- currentIndex: -1,
- articleList: ['紅樓夢', '西遊記', '三國演義']
- }
- },
- methods: {
- onEmitIndex(idx) {
- this.currentIndex = idx
- }
- }
- }
- </script>
- <template>
- <div>
- <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
- </div>
- </template>
- <script>
- export default {
- props: ['articles'],
- methods: {
- emitIndex(index) {
- this.$emit('onEmitIndex', index)
- }
- }
- }
- </script>
2、 $children / $parent
上面這張圖片是vue官方的解釋,經過$parent和$children就能夠訪問組件的實例,拿到實例表明什麼?表明能夠訪問此組件的全部方法和data。接下來就是怎麼實現拿到指定組件的實例。
使用方法
- // 父組件中
- <template>
- <div class="hello_world">
- <div>{{msg}}</div>
- <com-a></com-a>
- <button @click="changeA">點擊改變子組件值</button>
- </div>
- </template>
- <script>
- import ComA from './test/comA.vue'
- export default {
- name: 'HelloWorld',
- components: { ComA },
- data() {
- return {
- msg: 'Welcome'
- }
- },
- methods: {
- changeA() {
- // 獲取到子組件A
- this.$children[0].messageA = 'this is new value'
- }
- }
- }
- </script>
- // 子組件中
- <template>
- <div class="com_a">
- <span>{{messageA}}</span>
- <p>獲取父組件的值爲: {{parentVal}}</p>
- </div>
- </template>
- <script>
- export default {
- data() {
- return {
- messageA: 'this is old'
- }
- },
- computed:{
- parentVal(){
- return this.$parent.msg;
- }
- }
- }
- </script>
要注意邊界狀況,如在#app上拿$parent獲得的是new Vue()的實例,在這實例上再拿$parent獲得的是undefined,而在最底層的子組件拿$children是個空數組。也要注意獲得$parent和$children的值不同,$children 的值是數組,而$parent是個對象
總結
上面兩種方式用於父子組件之間的通訊, 而使用props進行父子組件通訊更加廣泛; 兩者皆不能用於非父子組件之間的通訊。
3、provide/ inject
概念:
provide/ inject 是vue2.2.0新增的api, 簡單來講就是父組件中經過provide來提供變量, 而後再子組件中經過inject來注入變量。
注意: 這裏不論子組件嵌套有多深, 只要調用了inject 那麼就能夠注入provide中的數據,而不侷限於只能從當前父組件的props屬性中回去數據
舉例驗證
接下來就用一個例子來驗證上面的描述:
假設有三個組件: A.vue、B.vue、C.vue 其中 C是B的子組件,B是A的子組件
- // A.vue
- <template>
- <div>
- <comB></comB>
- </div>
- </template>
- <script>
- import comB from '../components/test/comB.vue'
- export default {
- name: "A",
- provide: {
- for: "demo"
- },
- components:{
- comB
- }
- }
- </script>
- // B.vue
- <template>
- <div>
- {{demo}}
- <comC></comC>
- </div>
- </template>
- <script>
- import comC from '../components/test/comC.vue'
- export default {
- name: "B",
- inject: ['for'],
- data() {
- return {
- demo: this.for
- }
- },
- components: {
- comC
- }
- }
- </script>
- // C.vue
- <template>
- <div>
- {{demo}}
- </div>
- </template>
- <script>
- export default {
- name: "C",
- inject: ['for'],
- data() {
- return {
- demo: this.for
- }
- }
- }
- </script>
4、ref / refs
ref:若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子組件上,引用就指向組件實例,能夠經過實例直接調用組件的方法或訪問數據, 咱們看一個ref 來訪問組件的例子:
- // 子組件 A.vue
- export default {
- data () {
- return {
- name: 'Vue.js'
- }
- },
- methods: {
- sayHello () {
- console.log('hello')
- }
- }
- }
- // 父組件 app.vue
- <template>
- <component-a ref="comA"></component-a>
- </template>
- <script>
- export default {
- mounted () {
- const comA = this.$refs.comA;
- console.log(comA.name); // Vue.js
- comA.sayHello(); // hello
- }
- }
- </script>
5、eventBus
eventBus 又稱爲事件總線,在vue中可使用它來做爲溝通橋樑的概念, 就像是全部組件共用相同的事件中心,能夠向該中心註冊發送事件或接收事件, 因此組件均可以通知其餘組件。
eventBus也有不方便之處, 當項目較大,就容易形成難以維護的災難
在Vue的項目中怎麼使用eventBus來實現組件之間的數據通訊呢?具體經過下面幾個步驟
1. 初始化
首先須要建立一個事件總線並將其導出, 以便其餘模塊可使用或者監聽它.
- // event-bus.js
- import Vue from 'vue'
- export const EventBus = new Vue()
2. 發送事件
假設你有兩個組件: additionNum 和 showNum, 這兩個組件能夠是兄弟組件也能夠是父子組件;這裏咱們以兄弟組件爲例:
- <template>
- <div>
- <show-num-com></show-num-com>
- <addition-num-com></addition-num-com>
- </div>
- </template>
- <script>
- import showNumCom from './showNum.vue'
- import additionNumCom from './additionNum.vue'
- export default {
- components: { showNumCom, additionNumCom }
- }
- </script>
- // addtionNum.vue 中發送事件
- <template>
- <div>
- <button @click="additionHandle">+加法器</button>
- </div>
- </template>
- <script>
- import {EventBus} from './event-bus.js'
- console.log(EventBus)
- export default {
- data(){
- return{
- num:1
- }
- },
- methods:{
- additionHandle(){
- EventBus.$emit('addition', {
- num:this.num++
- })
- }
- }
- }
- </script>
3. 接收事件
- // showNum.vue 中接收事件
- <template>
- <div>計算和: {{count}}</div>
- </template>
- <script>
- import { EventBus } from './event-bus.js'
- export default {
- data() {
- return {
- count: 0
- }
- },
- mounted() {
- EventBus.$on('addition', param => {
- thisthis.count = this.count + param.num;
- })
- }
- }
- </script>
這樣就實現了在組件addtionNum.vue中點擊相加按鈕, 在showNum.vue中利用傳遞來的 num 展現求和的結果.
4. 移除事件監聽者
若是想移除事件的監聽, 能夠像下面這樣操做:
- import { eventBus } from 'event-bus.js'
- EventBus.$off('addition', {})
6、Vuex
1. Vuex介紹
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化.
Vuex 解決了多個視圖依賴於同一狀態和來自不一樣視圖的行爲須要變動同一狀態的問題,將開發者的精力聚焦於數據的更新而不是數據在組件之間的傳遞上
2. Vuex各個模塊
- state:用於數據的存儲,是store中的惟一數據源
- getters:如vue中的計算屬性同樣,基於state數據的二次包裝,經常使用於數據的篩選和多個數據的相關性計算
- mutations:相似函數,改變state數據的惟一途徑,且不能用於處理異步事件
- actions:相似於mutation,用於提交mutation來改變狀態,而不直接變動狀態,能夠包含任意異步操做
- modules:相似於命名空間,用於項目中將各個模塊的狀態分開定義和操做,便於維護
3. Vuex實例應用
- // 父組件
- <template>
- <div id="app">
- <ChildA/>
- <ChildB/>
- </div>
- </template>
- <script>
- import ChildA from './components/ChildA' // 導入A組件
- import ChildB from './components/ChildB' // 導入B組件
- export default {
- name: 'App',
- components: {ChildA, ChildB} // 註冊A、B組件
- }
- </script>
- // 子組件childA
- <template>
- <div id="childA">
- <h1>我是A組件</h1>
- <button @click="transform">點我讓B組件接收到數據</button>
- <p>由於你點了B,因此個人信息發生了變化:{{BMessage}}</p>
- </div>
- </template>
- <script>
- export default {
- data() {
- return {
- AMessage: 'Hello,B組件,我是A組件'
- }
- },
- computed: {
- BMessage() {
- // 這裏存儲從store裏獲取的B組件的數據
- return this.$store.state.BMsg
- }
- },
- methods: {
- transform() {
- // 觸發receiveAMsg,將A組件的數據存放到store裏去
- this.$store.commit('receiveAMsg', {
- AMsg: this.AMessage
- })
- }
- }
- }
- </script>
- // 子組件 childB
- <template>
- <div id="childB">
- <h1>我是B組件</h1>
- <button @click="transform">點我讓A組件接收到數據</button>
- <p>由於你點了A,因此個人信息發生了變化:{{AMessage}}</p>
- </div>
- </template>
- <script>
- export default {
- data() {
- return {
- BMessage: 'Hello,A組件,我是B組件'
- }
- },
- computed: {
- AMessage() {
- // 這裏存儲從store裏獲取的A組件的數據
- return this.$store.state.AMsg
- }
- },
- methods: {
- transform() {
- // 觸發receiveBMsg,將B組件的數據存放到store裏去
- this.$store.commit('receiveBMsg', {
- BMsg: this.BMessage
- })
- }
- }
- }
- </script>
vuex的store,js
- import Vue from 'vue'
- import Vuex from 'vuex'
- Vue.use(Vuex)
- const state = {
- // 初始化A和B組件的數據,等待獲取
- AMsg: '',
- BMsg: ''
- }
- const mutations = {
- receiveAMsg(state, payload) {
- // 將A組件的數據存放於state
- state.AMsg = payload.AMsg
- },
- receiveBMsg(state, payload) {
- // 將B組件的數據存放於state
- state.BMsg = payload.BMsg
- }
- }
- export default new Vuex.Store({
- state,
- mutations
- })
7、 localStorage / sessionStorage
這種通訊比較簡單,缺點是數據和狀態比較混亂,不太容易維護。
經過window.localStorage.getItem(key) 獲取數據
經過window.localStorage.setItem(key,value) 存儲數據
注意用JSON.parse() / JSON.stringify() 作數據格式轉換
localStorage / sessionStorage能夠結合vuex, 實現數據的持久保存,同時使用vuex解決數據和狀態混亂問題.
八 $attrs與 $listeners
如今咱們來討論一種狀況, 咱們一開始給出的組件關係圖中A組件與D組件是隔代關係, 那它們以前進行通訊有哪些方式呢?
- 使用props綁定來進行一級一級的信息傳遞, 若是D組件中狀態改變須要傳遞數據給A, 使用事件系統一級級往上傳遞
- 使用eventBus,這種狀況下仍是比較適合使用, 可是碰到多人合做開發時, 代碼維護性較低, 可讀性也低
- 使用Vuex來進行數據管理, 可是若是僅僅是傳遞數據, 而不作中間處理,使用Vuex處理感受有點大材小用了.
在vue2.4中,爲了解決該需求,引入了$attrs 和$listeners , 新增了inheritAttrs 選項。 在版本2.4之前,默認狀況下,父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外),將會「回退」且做爲普通的HTML特性應用在子組件的根元素上。接下來看一個跨級通訊的例子:
- // app.vue
- // index.vue
- <template>
- <div>
- <child-com1
- :name="name"
- :age="age"
- :gender="gender"
- :height="height"
- title="程序員成長指北"
- ></child-com1>
- </div>
- </template>
- <script>
- const childCom1 = () => import("./childCom1.vue");
- export default {
- components: { childCom1 },
- data() {
- return {
- name: "zhang",
- age: "18",
- gender: "女",
- height: "158"
- };
- }
- };
- </script>
- // childCom1.vue
- <template class="border">
- <div>
- <p>name: {{ name}}</p>
- <p>childCom1的$attrs: {{ $attrs }}</p>
- <child-com2 v-bind="$attrs"></child-com2>
- </div>
- </template>
- <script>
- const childCom2 = () => import("./childCom2.vue");
- export default {
- components: {
- childCom2
- },
- inheritAttrs: false, // 能夠關閉自動掛載到組件根元素上的沒有在props聲明的屬性
- props: {
- name: String // name做爲props屬性綁定
- },
- created() {
- console.log(this.$attrs);
- // { "age": "18", "gender": "女", "height": "158", "title": "程序員成長指北" }
- }
- };
- </script>
- // childCom2.vue
- <template>
- <div class="border">
- <p>age: {{ age}}</p>
- <p>childCom2: {{ $attrs }}</p>
- </div>
- </template>
- <script>
- export default {
- inheritAttrs: false,
- props: {
- age: String
- },
- created() {
- console.log(this.$attrs);
- // { "gender": "女", "height": "158", "title": "程序員成長指北" }
- }
- };
- </script>
總結
常見使用場景能夠分爲三類:
- 父子組件通訊: props; $parent / $children; provide / inject ; ref ; $attrs / $listeners
- 兄弟組件通訊: eventBus ; vuex
- 跨級通訊: eventBus;Vuex;provide / inject 、$attrs / $listeners