vue中組件之間的通信


組件可以有以下幾種關系:

A-B、B-C、B-D都是父子關系

C-D是兄弟關系

A-C、A-D是隔代關系

不同使用場景,如何選擇有效的通信方式 ?vue組件中通信的幾種方式 ?

1. props ★★

2. $emit/$on ★★ 事件總線

3. vuex ★★★

4.$parent/$children

5. $attrs/$listeners

6. provide/inject ★★★

vue中組件之間通信?

常見使用場景可以分為三類:

  • 父子組件通信
  • 兄弟組件通信
  • 跨層組件通信
  1. 分幾種
  2. 使用
  3. 選用

最后說一下在項目中怎么選用:比如我們我們項目中只是涉及到簡單的數據數據傳遞選擇props,其次如果項目中需要保存狀態的時候選用vuex等等,只是說一個簡單例子!

方法一、props


父組件A通過props向子組件B傳遞值, B組件傳遞A組件通過$emitA組件通過v-on/@觸發

1-1 父組件=>子組件傳值

// 父組件
<template>
    <div id="app">
        <Child v-bind:child="users"></Child> //前者自定義名稱便于子組件調用,后者要傳遞數據名
    </div>
</template>
<script>
    import Child from "./components/Child" //子組件
    export default {
        name: 'App',
        data(){
            return{
              users:["Eric","Andy","Sai"]
            }
        },
        components:{
            "Child":Child
        }
    }
</script>
// 子組件
<template>
    <div class="hello">
        <ul>
            <li v-for="item in child">{{ item }}</li> //遍歷傳遞過來的值渲染頁面
        </ul>
    </div>
</template>
<script>
    export default {
        name: 'Hello World',
        props:{
            child:{           //這個就是父組件中子標簽自定義名字
              type:Array,     //對傳遞過來的值進行校驗
              required:true   //必添
            }
          }
    }
</script>

總結:父組件通過props向下傳遞數據給子組件。

1-2子組件=>父組件傳值

// 子組件 Header.vue
<template>
  <div>
    <h1 @click="changeTitle">{{ title }}</h1> //綁定一個點擊事件
  </div>
</template>
<script>
    export default {
      name: 'header',
      data() {
        return {
          title:"Vue.js Demo"
        }
      },
      methods:{
        changeTitle() {
          this.$emit("titleChanged","子向父組件傳值"); //自定義事件  傳遞值“子向父組件傳值”
        }
      }
    }
</script>
// 父組件
<template>
  <div id="app">
    <header v-on:titleChanged="updateTitle"></header>
    //與子組件titleChanged自定義事件保持一致
   // updateTitle($event)接受傳遞過來的文字
    <h2>{{ title }}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
    export default {
      name: 'App',
      data(){
        return{
          title:"傳遞的是一個值"
        }
      },
      methods:{
        updateTitle(e){   //聲明這個函數
          this.title = e;
        }
      },
      components:{
       "app-header":Header,
      }
    }
</script>
總結:子組件通過events給父組件發送消息,實際上就是子組件把自己的數據發送到父組件。

方法二、$emit/$on => $bus


vue實例 作為事件總線(事件中心)用來觸發事件和監聽事件,可以通過此種方式進行組件間通信包括:父子組件、兄弟組件、跨級組件

例:

創建bus文件

import Vue from 'vue'

export defult new Vue()
// gg組件
<template id="a">
  <div>
    <h3>gg組件</h3>
    <button @click="sendMsg">將數據發送給dd組件</button>
  </div>
</template>
<script>
import bus from './bus'
export default {
    methods: {
        sendMsg(){
            bus.$emit('sendTitle','傳遞的值')
        }
    }
}
</script>
// dd組件
<template>
    <div>
        接收gg傳遞過來的值:{{msg}}
    </div>
</template>
<script>
import bus from './bus'
export default {
    data(){
        return {
            mag: ''
        }
    }
    mounted(){
        bus.$on('sendTitle',(val)=>{
            this.mag = val
        })
    }
}
</script>

方法三、vuex


1-1 vuex介紹

Vuex實現了一個單向數據流,在全局擁有一個State存放數據,當組件要更改State中的數據時,必須通過Mutation提交修改信息,Mutation同時提供了訂閱者模式供外部插件調用獲取State數據的更新。
而當所有異步操作(常見于調用后端接口異步獲取更新數據)或批量的同步操作需要走Action,但Action也是無法直接修改State的,還是需要通過Mutation來修改State的數據。最后,根據State的變化,渲染到視圖上。

1-2 vuex中核心概念

  • statevuex的唯一數據源,如果獲取多個state,可以使用...mapState
    export const store = new Vuex.Store({   
    // 注意Store的S大寫
    <!-- 狀態儲存 -->
       state: {
            productList: [
               {
               name: 'goods 1',
               price: 100
                   
               }
           ]
       }
    })
    
  • getter: 可以將getter理解為計算屬性,getter的返回值根據他的依賴緩存起來,依賴發生變化才會被重新計算。
    import Vue from 'vue'
    import Vuex from 'vuex';
    Vue.use(Vuex)
    
    export const store = new Vuex.Store({ 
        state: {
            productList: [
                {
                name: 'goods 1',
                price: 100
                },
            ]
        },
        // 輔助對象 mapGetter
        getters: {
            getSaledPrice: (state) => {
                let saleProduct = state.productList.map((item) => {
                    return {
                        name: '**' + item.name + '**',
                        price: item.price / 2
                    }
                })
                return saleProduct;
            }
        }
    })
    
    // 獲取getter計算后的值
    export default {
        data () {
            return {
                productList : this.$store.getters.getSaledPrice 
            }
        }
    }
    
  • mutation:更改vuexstate中唯一的方是提交mutation都有一個字符串和一個回調函數。回調函數就是使勁進行狀態修改的地方。并且會接收state作為第一個參數payload為第二個參數,payload為自定義函數,mutation必須是同步函數。
    // 輔助對象 mapMutations
    mutations: {
        <!-- payload 為自定義函數名-->
        reducePrice: (state, payload) => {
            return state.productList.forEach((product) => {
                product.price -= payload;
            })
        }
    }
    
    <!-- 頁面使用 -->
    methods: {
        reducePrice(){
            this.$store.commit('reducePrice', 4)
        }
    }
    
  • actionaction類似mutation都是修改狀態,不同之處,

    action提交的mutation不是直接修改狀態
    action可以包含異步操作,而mutation不行
    action中的回調函數第一個參數是context,是一個與store實例具有相同屬性的方法的對象
    action通過store.dispatch觸發,mutation通過store.commit提交
    actions: { // 提交的是mutation,可以包含異步操作 reducePriceAsync: (context, payload) => { setTimeout(()=> { context.commit('reducePrice', payload); // reducePrice為上一步mutation中的屬性 },2000) } }
    // 輔助對象 mapActions methods: { reducePriceAsync(){ this.$store.dispatch('reducePriceAsync', 2) }, }

  • module:由于是使用單一狀態樹,應用的所有狀態集中到比較大的對象,當應用變得非常復雜是,store對象就有可能變得相當臃腫。為了解決以上問題,vuex允許我們將store分割成模塊,每個模塊擁有自己的state,mutation,action,getter,甚至是嵌套子模塊從上至下進行同樣方式分割。
    const moduleA = {
        state: {...},
        mutations: {...},
        actions: {...},
        getters: {...}
    }
    const moduleB = {
        state: {...},
        mutations: {...},
        actions: {...},
        getters: {...}
    }
    const store = new Vuex.Store({
        a: moduleA,
        b: moduleB
    })
    store.state.a
    store.state.b
    

1-3 vuex中數據存儲 localStorage

vuexvue 的狀態管理器,存儲的數據是響應式的。但是并不會保存起來,刷新之后就回到了初始狀態,具體做法應該在vuex里數據改變的時候把數據拷貝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的數據,取出來再替換store里的state

例:

let defaultCity = "上海"
try {    
// 用戶關閉了本地存儲功能,此時在外層加個try...catch
  if (!defaultCity){
  // f復制一份
        defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
        }
    }catch(e){
        console.log(e)
    }
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 數據改變的時候把數據拷貝一份保存到localStorage里面
      } catch (e) {}
    }
  }
})

注意:vuex里,保存的狀態,都是數組,而localStorage只支持字符串,所以需要用JSON轉換:

JSON.stringify(state.subscribeList)<font color="red">// array -> string</font>
JSON.parse(window.localStorage.getItem("subscribeList"))<font color="red">// string -> array</font>

方法四、$attr/$listeners


1-1 簡介

多級組件嵌套需要傳遞數據時,通常使用的方法是通過vuex。但如果僅僅是傳遞數據,而不做中間處理,使用 vuex 處理,未免有點大材小用。為此Vue2.4 版本提供了另一種方法----$attrs/$listeners
  • $attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入內部組件。通常配合 interitAttrs 選項一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件

例:

// index.vue
<template>
  <div>
    <h2>王者峽谷</h2>
    <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1>
  </div>
</template>
<script>
    const childCom1 = () => import("./childCom1.vue");
    export default {
      components: { childCom1 },
      data() {
        return {
          foo: "Javascript",
          boo: "Html",
          coo: "CSS",
          doo: "Vue"
        };
      }
    };
</script>
//childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</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: {
        foo: String // foo作為props屬性綁定
      },
      created() {
        console.log(this.$attrs); 
        // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
      }
    };
</script>
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); 
    // {"coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
    export default {
      props: {
        coo: String,
        title: String
      }
    };
</script>
所示$attrs表示沒有繼承數據的對象,格式為{屬性名:屬性值}。Vue2.4提供了$attrs , $listeners 來傳遞數據與事件,跨級組件之間的通訊變得更簡單。
簡單來說:$attrs與$listeners 是兩個對象,$attrs 里存放的是父組件中綁定的非 Props 屬性,$listeners里存放的是父組件中綁定的非原生事件。

方法五、provide/inject


1-1 簡介

Vue2.2.0新增API,這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關系成立的時間里始終生效。一言而蔽之:祖先組件中通過provider來提供變量,然后在子孫組件中通過inject來注入變量。
provide / inject API 主要解決了跨級組件間的通信問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間建立了一種主動提供與依賴注入的關系。

例:

//a.vue
export default {
    provide: {
        name: '王者峽谷' //這種綁定是不可響應的
    }
}
// b.vue
export default {
    inject: ['name'],
    mounted () {
        console.log(this.name) //輸出王者峽谷
    }
}

A.vue,我們設置了一個 provide:name,值為王者峽谷,將name這個變量提供給它的所有子組件。

B.vue ,通過 inject 注入了從A組件中提供的name變量,組件B中,直接通過this.name訪問這個變量了。

這就是 provide / inject API 最核心的用法。

需要注意的是:provide 和inject綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的對象,那么其對象的屬性還是可響應的----vue官方文檔,所以,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的。

1-2 provide與inject 怎么實現數據響應式

兩種方法:

1-2-1

  • provide祖先組件的實例,然后在子孫組件中注入依賴,這樣就可以在子孫組件中直接修改祖先組件的實例的屬性,不過這種方法有個缺點就是這個實例上掛載很多沒有必要的東西比如props,methods

1-2-2

  • 使用2.6最新API Vue.observable 優化響應式 provide(推薦)

例:

組件D、E和F獲取A組件傳遞過來的color值,并能實現數據響應式變化,即A組件的color變化后,組件D、E、F會跟著變(核心代碼如下:)

// A 組件 
<div>
      <h1>A 組件</h1>
      <button @click="() => changeColor()">改變color</button>
      <ChildrenB />
      <ChildrenC />
</div>
......
  data() {
    return {
      color: "blue"
    };
  },
  // provide() {
  //   return {
  //     theme: {
  //       color: this.color //這種方式綁定的數據并不是可響應的
  //     } // 即A組件的color變化后,組件D、E、F不會跟著變
  //   };
  // },
  provide() {
    return {
      theme: this//方法一:提供祖先組件的實例
    };
  },
  methods: {
    changeColor(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
  // 方法二:使用2.6最新API Vue.observable 優化響應式 provide
  // provide() {
  //   this.theme = Vue.observable({
  //     color: "blue"
  //   });
  //   return {
  //     theme: this.theme
  //   };
  // },
  // methods: {
  //   changeColor(color) {
  //     if (color) {
  //       this.theme.color = color;
  //     } else {
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
  //     }
  //   }
  // }
// F 組件 
<template functional>
<div class="border2">
  <h3 :style="{ color: injections.theme.color }">F 組件</h3>
</div>
</template>
<script>
export default {
inject: {
  theme: {
    //函數式組件取值不一樣
    default: () => ({})
  }
}
};
</script>

注:provide 和 inject主要為高階插件/組件庫提供用例,能在業務中熟練運用,可以達到事半功倍的效果!

方法六、$parent / $children與 ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例
  • $parent / $children:訪問父 / 子實例

注意:這兩種都是直接得到組件實例,使用后可以直接調用組件的方法或訪問數據。我們先來看個用 ref來訪問組件的

例:

export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 彈窗
    }
  }
</script>

注:這兩種方法的弊端是,無法在跨級或兄弟間通信。

我們想在 component-a 中,訪問到引用它的頁面中(這里就是 parent.vue)的兩個 component-b 組件,那這種情況下,就得配置額外的插件或工具了,比如 Vuex 和 Bus 的解決方案。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。