vue的兩大特性是響應式程式設計和元件化。元件(Component)是 Vue 最核心的功能,但是各個元件範例的作用域是相互獨立的,這表明不同元件之間的資料是無法直接相互參照的。如果想要跨元件參照資料,就需要用到元件通訊了,在通訊之前先要理解元件之間的關係:
如上圖所示:
父子關係:A與B,A與C,B與D,C與E
兄弟關係:B與C
隔代關係(可能隔更多代):A與D,A與E
跨級關係:B與E,D與E等
props
/$emit
父元件通過v-bind
繫結一個自定義的屬性,子元件通過props
接收父元件傳來的資料;子元件通過$emit
觸發事件,父元件用on()
或者在子元件的自定義標籤上使用v-on
來監聽子元件觸發的自定義事件,從而接收子元件傳來的資料。(學習視訊分享:)
下面通過一個例子來說明父元件向子元件傳值,父元件parent.vue把資料books:['JavaScript高階程式設計', 'CSS新世界', '圖解 HTTP 彩色版']
傳給子元件child.vue,並在child.vue中展示出來
// 父元件parent.vue <template> <div> <Child :books="books"/> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'parent', components: { Child }, data() { return { books: ['JavaScript高階程式設計', 'CSS新世界', '圖解 HTTP 彩色版'] } } } </script>
// 子元件child.vue <template> <div> <ul> <li v-for="(item, index) in books" :key="index">{{item}}</li> </ul> </div> </template> <script> export default { props: { books: { type: Array, default: () => { return [] } } } } </script>
注意:通過props傳遞資料是單向的,父元件資料變化時會傳遞給子元件,但子元件不能通過修改props傳過來的資料來修改父元件的相應狀態,即所謂的單向資料流。
下面通過子元件點選書籍列表,用$emit()
觸發,然後再父元件中獲取
// 子元件child.vue <template> <div> <ul> <li v-for="(item, index) in books" :key="index" @click="like(item)">{{item}}</li> </ul> </div> </template> <script> export default { props: { books: { type: Array, default: () => { return [] } } }, methods: { like(item) { this.$emit('likeBook', item) } } } </script>
// 父元件parent.vue <template> <div> <Child :books="books" @likeBook="likeBook"/> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'parent', components: { Child }, data() { return { books: ['JavaScript高階程式設計', 'CSS新世界', '圖解 HTTP 彩色版'] } }, methods: { likeBook(val) { alert('我最喜歡的書籍是《' + val + '》') } } } </script>
$parent
/$children
// 父元件parent.vue <template> <div> <Child /> <button @click="getChildData">獲取子元件資料</button> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'parent', components: { Child }, data() { return { books: ['JavaScript高階程式設計', 'CSS新世界', '圖解 HTTP 彩色版'] } }, methods: { getChildData() { alert(this.$children[0].msg) } } } </script>
// 子元件child.vue <template> <div> <ul> <li v-for="(item, index) in bookLists" :key="index">{{item}}</li> </ul> </div> </template> <script> export default { name: 'child', data() { return { bookLists: [], msg: '我是子元件的值!' } }, mounted() { this.bookLists = this.$parent.books } } </script>
注意:$parent
拿到的是物件,如果是最頂層沒有父元件的情況下拿到的是undefined
;$children
拿到的是陣列,如果是做底層沒有子元件的情況下,拿到的是空陣列;這兩種通訊方式只能用於父子元件通訊
ref
ref如果在普通Dom元素上使用,參照指向的就是 DOM 元素;如果在子元件上使用,參照就指向元件範例,可以通過範例直接呼叫元件的方法和資料
// 父元件parent.vue <template> <div> <Child ref="child" /> <button @click="getChildData">獲取子元件資料</button> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'parent', components: { Child }, methods: { getChildData() { const msg = this.$refs['child'].msg console.log(msg) this.$refs['child'].say() } } } </script>
// 子元件child.vue <script> export default { name: 'child', data() { return { msg: '我是子元件的值!' } }, methods: { say() { alert('你好,我是子元件!') } }, } </script>
provide
/inject
祖先元件通過provide
來提供變數,子孫元件通過inject
注入變數來獲取祖先元件的資料,不管子孫元件巢狀有多深, 只要呼叫了inject 那麼就可以注入provide中的資料。下面是具體程式碼:
// 父元件 <template> <div> <h1>康熙</h1> <Son /> </div> </template> <script> import Son from './components/Son.vue' export default { components: { Son }, provide() { return { FatherToSon: this.FatherToSon, FatherToGrandson: this.FatherToGrandson, } }, data() { return { FatherToSon: '我是康熙,雍正,你是我兒子!', FatherToGrandson: '我是康熙,乾隆,你是我孫子!', } } } </script>
// 子元件 <template> <div> <h1>雍正</h1> <button @click="receive">接收</button> <Grandson /> </div> </template> <script> import Grandson from './Grandson.vue' export default { components: { Grandson }, inject: ['FatherToSon'], methods: { receive() { alert(this.FatherToSon) } } } </script>
// 孫元件 <template> <div> <h1>乾隆</h1> <button @click="receive">接收</button> </div> </template> <script> export default { inject: ['FatherToGrandson'], methods: { receive() { alert(this.FatherToGrandson) } } } </script>
注意:provide/inject只能從上往下傳值,且不是響應式,若要變成響應式的資料provide需要提供函數
eventBus
的$emit
/$on
eventBus是訊息傳遞的一種方式,基於一個訊息中心,訂閱和釋出訊息的模式,稱為釋出訂閱者模式。
eventBus 又稱為事件匯流排。在 Vue 中可使用 eventBus 來作為溝通橋樑的概念,就像是所有元件共用相同的事件中心,可向該中心註冊傳送事件或接收事件,所以元件都可以上下平行地通知其他元件。
$emit('name',args)
: name:釋出的訊息名稱 , args:釋出的訊息$on('name',fn)
: name:訂閱的訊息名稱, fn: 訂閱的訊息$once('name',fn)
: name:訂閱的訊息名稱, fn: 訂閱的訊息。與$on相似但是隻觸發一次,一旦觸發之後,監聽器就會被移除$off('name',callback)
:name:事件名稱,callback:回撥監聽器
eventbus可以實現任何元件之前的通訊,下面以兄弟元件為例
// main.js // 全域性新增事件匯流排 Vue.prototype.$bus = new Vue()
在parent.vue引入ChildA和ChildB元件,使它們成為兄弟元件
// 父元件parent.vue <template> <div> <ChildA /> <ChildB /> </div> </template> <script> import ChildA from './components/childA' import ChildB from './components/childB' export default { components: { ChildA, ChildB } } </script>
在ChildA元件中用$emit
傳送事件
// ChildA元件 <template> <div> <h1>元件A</h1> <button @click="send">傳送</button> </div> </template> <script> export default { methods: { // 傳送事件 send() { this.$bus.$emit('message', '歡迎使用eventBus!') } } } </script>
在ChildB元件中用$on
接收ChildA傳送的事件
// ChildB元件 <template> <div> <h1>元件B</h1> </div> </template> <script> export default { mounted() { // 接收事件 this.$bus.$on('message', data => { alert('我是元件B,我收到的訊息為:' + data) }) }, beforeDestroy() { this.$bus.$off('message') } } </script>
注意:$on
監聽的事件不會自動移除監聽,因此在不用時最好使用$off
移除監聽以免產生問題
$attrs
/$listeners
當元件為兩級巢狀時,一般採用props
和$emit
,但遇到多級元件巢狀時這種方法就不太適用了,如果不做中間處理,只傳遞資料用vuex有點大材小用了。因此在vue2.4
中為了解決這一需求,便引入了$attrs
和$listeners
, 新增了inheritAttrs
屬性
$attrs
:當父元件傳遞了很多資料給子元件時,子元件沒有宣告props來進行接收,麼子元件中的attrs屬性就包含了所有父元件傳來的資料(除開已經props宣告了的);子元件還可以使用v−bind="$attrs"
的形式將所有父元件傳來的資料(除開已經props宣告了的)傳向下一級子元件,通常和interitAttrs
屬性一起使用。$listeners
:包含了父元件中(不含.native
修飾器的)v-on 事件監聽器,通過v-on="$listeners"
,可以將這些事件繫結給它自己的子元件
下面看一個例子:
// 父元件 <template> <div> <ChildA :name="name" :sex="sex" :age="age" @getName="getName" @getAge="getAge" /> </div> </template> <script> import ChildA from './components/childA' export default { name: 'parent', components: { ChildA, }, data() { return { name: '小明', age: 18, sex: '男' } }, methods: { // 獲取名字 getName() { console.log('我的名字是' + this.name) }, // 獲取年齡 getAge() { console.log('我今年' + this.age + '歲'); } } } </script>
// 子元件A <template> <div> <h1>元件A</h1> {{ msgA }} <hr/> <ChildB v-bind="$attrs" :height="height" v-on="$listeners" @getHeight="getHeight" /> </div> </template> <script> import ChildB from './childB.vue' export default { name: 'ChildA', components: { ChildB }, data() { return { msgA: null, height: '175cm' } }, props: { sex: { type: String, default: '' } }, mounted() { this.msgA = this.$attrs console.log('元件A獲取的$listeners:', this.$listeners) }, methods: { // 獲取身高 getHeight() { console.log('我的身高是' + this.height); } } } </script>
// 孫元件B <template> <div> <h1>元件B</h1> {{ msgB }} </div> </template> <script> export default { name: 'ChildB', data() { return { msgB: null } }, mounted() { this.msgB = this.$attrs console.log('元件B獲取的$listeners:', this.$listeners) } } </script>
$attrs獲取的結果:
$listeners獲取的結果:
如程式碼和圖所示元件A中props
宣告接收了sex屬性,因此元件中$attrs
獲取的是父元件中繫結的除去sex屬性的值;元件A中使用了v-bind="$attrs"
和v-on="$listeners"
,則元件B獲取不僅是元件A中本身繫結的屬性和方法還包含元件A獲取父元件繫結的屬性和方法
如果父元件傳遞了很多引數給子元件,而子元件沒有用props完全接收,那麼沒有接收的這些屬性作為普通的 HTML attribute
應用在子元件的根元素上
如果你不希望子元件的根元素繼承特性,你可以在元件的選項中設定inheritAttrs: false
以上面的元件B為例,當inheritAttrs為true(inheritAttrs預設為true)
當inheritAttrs為false時
// 孫元件B export default { name: 'ChildB', inheritAttrs: false, data() { return { msgB: null } }, mounted() { this.msgB = this.$attrs console.log('元件B獲取的$listeners:', this.$listeners) } }
Vuex
Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式 + 庫。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
狀態管理包含以下幾個部分:
檢視發生變化會導致資料來源的改變,資料來源發生變化則會改變檢視,則上面表示是一個「單向資料流」。但是當我們的應用遇到多個元件共用狀態時,單向資料流的簡潔性很容易被破壞:
因此,為了解決這種問題我們把元件的共用狀態抽取出來,以一個全域性單例模式管理。在這種模式下,我們的元件樹構成了一個巨大的「檢視」,不管在樹的哪個位置,任何元件都能獲取狀態或者觸發行為!
通過定義和隔離狀態管理中的各種概念並通過強制規則維持檢視和狀態間的獨立性,我們的程式碼將會變得更結構化且易維護。
1、state
:儲存應用中需要共用的狀態,是Vuex中的唯一資料來源。
2、getters
:類似Vue中的計算屬性computed
,getter 的返回值會根據它的依賴被快取起 來,且只有當它的依賴值發生了改變才會被重新計算。
3、mutations
:更改 Vuex 的 store 中的狀態(state)的唯一方法,且mutation 必須是同步函數
4、actions
:類似於 mutation,提交的是 mutation,而不是直接變更狀態;可以包含任意非同步操作
5、modules
:將 store 分割成模組(module)。每個模組擁有自己的 state、mutation、action、getter、甚至是巢狀子模組——從上至下進行同樣方式的分割
// 父元件 <template> <div class="home"> <h1>父元件</h1> <hr/> <ChildA /> <hr/> <ChildB /> </div> </template> <script> import ChildA from './components/ChildA' import ChildB from './components/ChildB' export default { name: 'parent', components: { ChildA, ChildB } } </script>
// 子元件A <template> <div> <h1>元件A</h1> <p>A獲取的值: {{ count }}</p> <button @click="add(5)">ChildA-add</button> </div> </template> <script> export default { computed: { count() { return this.$store.state.count } }, methods: { // 改變store裡count的值 add(num) { this.$store.dispatch('countAdd', num) } } } </script> <style> </style>
// 子元件B <template> <div> <h1>元件B</h1> <p>B獲取的值: {{ countB }}</p> <button @click="add(10)">ChildB-add</button> </div> </template> <script> import { mapMutations, mapGetters } from 'vuex' export default { computed: { ...mapGetters({ countB: 'getCount' }) }, methods: { ...mapMutations(['countAdd']), // 改變store裡count的值 add(num) { this.countAdd(num) } } } </script> <style> </style>
store.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0, }, getters: { getCount: (state) => { return state.count } }, mutations: { countAdd(state, num) { state.count += num } }, actions: { countAdd(context, num) { context.commit('countAdd', num) } }, modules: { } })
localStorage
/sessionStorage
localStorage:本地儲存物件,儲存的資料是永久性資料,頁面重新整理,即使瀏覽器重新啟動,除非主動刪除不然儲存的資料會一直存在
sessionStorage:與localStorage相似,但是隻有在當前頁面下有效,關閉頁面或瀏覽器儲存的資料將會清空
localStorage和sessionStorage常用的API:
setItem (key, value) —— 儲存資料,以鍵值對的方式儲存資訊。 getItem (key) —— 獲取資料,將鍵值傳入,即可獲取到對應的value值。 removeItem (key) —— 刪除單個資料,根據鍵值移除對應的資訊。 clear () —— 刪除所有的資料 key (index) —— 獲取某個索引的key
// 儲存 setItem() { window.localStorage.setItem('name1', '小明') window.sessionStorage.setItem('name2', '小紅') }
// 接收 receive() { const name1 = window.localStorage.getItem('name1') const name2 = window.sessionStorage.getItem('name2') console.log(name1) // 列印結果為:小明 console.log(name2) // 列印結果為:小紅 }
localStorage和sessionStorage通過setItem()
儲存資料會自動轉換為String
型別,但是通過getItem()
其型別並不會轉換回來(localStorage和sessionStorage使用方法一樣,下面均以localStorage為例)
const num = 1 window.localStorage.setItem('num', num) const numRec = window.localStorage.getItem('num') console.log(numRec, typeof(numRec)) // 1 string
因此正確的儲存方式應該為:儲存之前用JSON.stringify()
方法將資料轉換成json字串
形式;需要使用資料的時候用JSON.parse()
方法將之前儲存的字串轉換成json物件
const num = 1 window.localStorage.setItem('num', JSON.stringify(num)) const obj = { name: '小紅', age: 18 } window.localStorage.setItem('obj', JSON.stringify(obj)) const numRec = JSON.parse(window.localStorage.getItem('num')) console.log(numRec, typeof(numRec)) // 1 'number' const objRec = JSON.parse(window.localStorage.getItem('obj')) console.log(objRec, typeof(objRec)) // {name: '小紅', age: 18} 'object'
注意:localStorage.setItem()和sessionStorage.setItem()不能直接儲存物件,必須使用JSON.stringify()
和JSON.parse()
轉換實現
以上8種通訊方式主要應用在以下三類場景:
props
/$emit
,單一的父子元件通訊使用$parent>
/$children
也比較方便;父元件也常使用ref
獲取子元件範例;也可使用provide
/inject
、$attrs
/$listeners
以及localStorage
/sessionStorage
eventBus
的$emit
/$on
;複雜的資料使用Vuex
比較方便;也可以使用localStorage
/sessionStorage
;provide
/inject
和$attrs
/$listeners
;跨級元件通訊的資料如果不復雜可使用eventBus
和localStorage
/sessionStorage
;如果資料複雜可使用Vuex
,但是要注意重新整理介面Vuex儲存的資料會消失本篇文章只是簡單記錄了一下平時使用的元件通訊方式,並沒有深入的介紹其細節,如果有錯誤的地方歡迎指正
(學習視訊分享:、)
以上就是Vue元件間怎麼通訊?元件通訊的幾種方式的詳細內容,更多請關注TW511.COM其它相關文章!