寫在前面
元件間的通訊是是實際開發中非常常用的一環,如何使用對專案整體設計、開發、規範都有很實際的的作用,我在專案開發中對此深有體會,總結下vue元件間通訊的幾種方式,討論下各自的使用場景
文章對相關場景預覽
- 父->子元件間的資料傳遞
- 子->父元件間的資料傳遞
- 兄弟元件間的資料傳遞
- 元件深層巢狀,祖先元件與子元件間的資料傳遞
文章相關技術預覽 prop、emit、bus、vuex、路由URL、provide/inject、children/$parent、$attrs/inheritAttrs
注:以下介紹與程式碼環境:vue2.0+、vue-cli2
父->子元件間的資料傳遞
父子元件的通訊是開發是最常用的也是最重要的,你們一定知道父子通訊是用prop傳遞資料的,像這樣:
//父元件,傳遞資料
<editor :inputIndex="data" :inputName="王文健"></editor>
複製程式碼
//子元件,接受資料,定義傳遞資料的型別type與預設值default
props: {
inputIndex: {
type: Object,
default: function(){
return {}
}
},
inputName: {
type: String,
default: ''
},
複製程式碼
注意項:
-
父元件傳遞資料時類似在標籤中寫了一個屬性,如果是傳遞的資料是data中的自然是要在傳遞屬性前加v-bind:,如果傳遞的是一個已知的固定值呢
- 字串是靜態的可直接傳入無需在屬性前加v-bind
- 數字,布林,物件,陣列,因為這些是js表示式而不是字串,所以即使這些傳遞的是靜態的也需要加v-bind,把資料放到data中引用,
-
如果prop傳到子元件中的資料是一個物件的話,要注意傳遞的是一個物件引用,雖然父子元件看似是分離的但最後都是在同一物件下
- 如果prop傳到子元件的值只是作為初始值使用,且在父元件中不會變化賦值到data中使用
- 如果傳到子元件的prop的資料在父元件會被改變的,放到計算屬性中監聽變化使用。因為如果傳遞的是個物件的話,只改變下面的某個屬性子元件中是不會響應式更新的,如果子元件需要在資料變化時響應式更新那隻能放到computed中或者用watch深拷貝deep:true才能監聽到變化
- 當然如果你又需要在子元件中通過prop傳遞資料的變化做些操作,那麼寫在computed中會報警告,因為計算屬性中不推薦有任何資料的改變,最好只進行計算。如果你非要進行資料的操作那麼可以把監聽寫在watch(注意deep深拷貝)或者使用computed的get和set如下圖:
- 但問題又來了,如果你傳進來的是個物件,同時你又需要在子元件中操作傳進來的這個資料,那麼在父元件中的這個資料也會改變,因為你傳遞的只是個引用, 即使你把prop的資料複製到data中也是一樣的,無論如何賦值都是引用的賦值,你只能對物件做深拷貝建立一個副本才能繼續操作,你可以用JSON的方法先轉化字串在轉成物件更方便一點,
- 所以在父子傳遞資料時要先考慮好資料要如何使用,否則你會遇到很多問題或子元件中修改了父元件中的資料,這是很隱蔽並且很危險的
子->父元件間的資料傳遞
在vue中子向父傳遞資料一般用**$emit**自定義事件,在父元件中監聽這個事件並在回撥中寫相關邏輯
// 父元件監聽子元件定義的事件
<editor :inputIndex="index" @editorEmit='editorEmit'></editor>
複製程式碼
// 子元件需要返回資料時執行,並可以傳遞資料
this.$emit('editorEmit', data)
複製程式碼
那麼問題來了,我是不是真的有必要去向父元件返回這個資料,用自定義事件可以在當子元件想傳遞資料或向子元件傳遞的資料有變化需要重新傳遞時執行,那麼另外一種場景,父元件需要子元件的一個資料但子元件並不知道或者說沒有能力在父元件想要的時候給父元件,那麼這個時候就要用到元件的一個選項ref:
<editor ref="editor" @editorEmit='editorEmit'></editor>
- 父元件在標籤中定義ref屬性,在js中直接呼叫this.$refs.editor就是呼叫整個子元件,子元件的所有內容都能通過ref去呼叫,當然我們並不推薦因為這會使資料看起來非常混亂,
- 所以我們可以在子元件中定義一種專供父元件呼叫的函式,,比如我們在這個函式中返回子元件data中某個資料,**當父元件想要獲取這個資料就直接主動呼叫ref執行這個函式獲取這個資料,**這樣能適應很大一部分場景,邏輯也更清晰一點
- 另外,父向子傳遞資料也可以用ref,有次需要在一個父元件中大量呼叫同一個子元件,而每次呼叫傳遞的prop資料都不同,並且傳遞資料會根據之後操作變化,這樣我需要在data中定義大量相關資料並改變它,我可以直接用ref呼叫子元件函式直接把資料以引數的形式傳給子元件,邏輯一下子清晰了
- 如果呼叫基礎元件可以在父元件中呼叫ref執行基礎元件中暴露的各種功能介面,比如顯示,消失等
兄弟元件間的資料傳遞
vue中兄弟元件間的通訊是很不方便的,或者說不支援的,那麼父子元件中都有什麼通訊方式呢
- 路由URL引數
- 在傳統開發時我們常常把需要跨頁面傳遞的資料放到url後面,跳轉到另外頁面時直接獲取url字串獲取想要的引數即可,在vue跨元件時一樣可以這麼做,
// router index.js 動態路由 { path:'/params/:Id', component:Params, name:Params } 複製程式碼
// 跳轉路由 <router-link :to="/params/12">跳轉路由</router-link> 複製程式碼
- 在跳轉後的元件中用$route.params.id去獲取到這個id引數為12,但這種只適合傳遞比較小的資料,數字之類的
- 在傳統開發時我們常常把需要跨頁面傳遞的資料放到url後面,跳轉到另外頁面時直接獲取url字串獲取想要的引數即可,在vue跨元件時一樣可以這麼做,
- Bus通訊
在元件之外定義一個bus.js作為元件間通訊的橋樑,適用於比較小型不需要vuex又需要兄弟元件通訊的
-
bus.js中新增如下
import Vue from 'vue' export default new Vue 複製程式碼
-
元件中呼叫bus.js通過自定義事件傳遞資料
import Bus from './bus.js' export default { methods: { bus () { Bus.$emit('msg', '我要傳給兄弟元件們') } } } 複製程式碼
-
兄弟元件中監聽事件接受資料
import Bus from './bus.js' export default { mounted() { Bus.$on('msg', (e) => { console.log(e) }) } } 複製程式碼
注:以上兩種使用場景並不高所以只是簡略提一下,這兩點都是很久以前寫過,以上例子網上直接蒐集而來如有錯誤,指正
- Vuex集中狀態管理
vuex是vue的集中狀態管理工具,對於大型應用統一集中管理資料,很方便,在此對vuex的用法並不過多介紹只是提一下使用過程中遇到的問題
-
規範:對於多人開發的大型應用規範的制定是至關重要的,對於所有人都會接觸到的vuex對其修改資料呼叫資料都應有一個明確嚴格的使用規範
- vuex分模組:專案不同模組間維護各自的vuex資料
- 限制呼叫:只允許action運算元據,getters獲取資料,使用mapGetters,mapActions輔助函式呼叫資料
-
對於vuex的使用場景也有一些爭論,有人認為正常元件之間就是要用父子元件傳值的方式,即使子元件需要使vuex中的資料也應該由父元件獲取再傳到子元件中,但有的時候元件間巢狀很深,只允許父元件獲取資料並不是一個方便的方法,所以對於祖先元元件與子元件傳值又有了新問題,vue官網也有一些方法解決,如下
-
祖先元件與子元件間的資料傳遞
provide/inject 除了正常的父子元件傳值外,vue也提供了provide/inject
這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效
官網例項
// 父級元件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子元件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
}
複製程式碼
- provide 選項應該是一個物件或返回一個物件的函式。該物件包含可注入其子孫的屬性。
- 一個字串陣列,或
一個物件,物件的 key 是本地的繫結名,value 是:
- 在可用的注入內容中搜尋用的 key (字串或 Symbol),或
一個物件,該物件的:
- from 屬性是在可用的注入內容中搜尋用的 key (字串或 Symbol)
- default 屬性是降級情況下使用的 value
- 在可用的注入內容中搜尋用的 key (字串或 Symbol),或
一個物件,該物件的:
提示:provide 和 inject 繫結並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的物件,那麼其物件的屬性還是可響應的。 具體細節移步vue相關介紹cn.vuejs.org/v2/api/#pro…
provide/inject還未在專案中應用過,後面會做嘗試
補充 $attrs/inheritAttrs
經小夥伴們提醒補充$attrs的使用
場景:祖先元件與子元件傳值
- 如果是props的話,就必須在子元件與祖先元件之間每個元件都要prop接受這個資料,再傳到下一層子元件,這就很麻煩,耦合深程式臃腫
- 如果用vuex確實顯得有點小題大做了,所以用$attrs直接去獲取祖先資料也不錯
包含了父作用域中不作為 prop 被識別 (且獲取) 的特性繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。
以上是官網對$attrs的解釋,我剛看我也是一臉懵逼,回去試了一下其實並不難,而且比較適用元件深層巢狀場景下,祖先元件向子元件傳值的問題
意思就是父元件傳向子元件傳的,子元件不prop接受的資料都會放在$attrs中,子元件直接用this.$attrs獲取就可以了。如過從父->孫傳,就在子元件中新增v-bind='$attrs',就把父元件傳來的子元件沒props接收的資料全部傳到孫元件,具體看以下程式碼
使用:
祖先元件
// 祖先元件
// 在祖先元件中直接傳入output和input
<template>
<div>
<child1 :output='output' :input="input"></child1>
</div>
</template>
<script>
import child1 from './child1.vue'
export default {
components: {
child1
},
data () {
return {
input: 'jijijijjijiji',
output: {
name: '王文健',
age: '18'
}
}
}
</script>
複製程式碼
子元件
<template>
<div
<h1>{{input}}</h1>
<child2 :child="child" v-bind='$attrs'></child2>
</div>
</template>
<script>
import child2 from './child2.vue'
export default {
components: {
child2
},
props: {
input: [String]
},
data () {
return {
child: 'child1child1child1child1s'
}
},
// 預設為true,如果傳入的屬性子元件沒有prop接受,就會以字串的形式出現為標籤屬性
// 設為false,在dom中就看不到這些屬性,試一下就知道了
inheritAttrs: false,
created () {
// 在子元件中列印的$attrs就是父元件傳入的值,刨去style,class,和子元件中已props的屬性
console.log(this.$attrs) // 列印output
}
}
</script>
複製程式碼
孫元件
<template>
<div>
{{$attrs.output.name}}
</div>
</template>
<script>
export default {
created () {
// 列印output和child
console.log(this.$attrs)
}
}
</script>
複製程式碼
看起來還是挺好用的,還沒在具體專案中用過,相信不久會用到的,如果還有什麼問題歡迎留言
$children/$parent
-
當然你可以直接用$children/$parent獲取當前元件的子元件例項或父元件例項(如果有的話),也能對其做些操作,不過並不推薦這麼做
-
你還可以放到localStorage,sessionStorage,cooikes之類的存在本地當然也能做到元件間的通訊
寫在結尾
文章只是整理一下筆記,談一談遇到的問題和經驗,並沒有嚴謹的措辭和詳細的過程,如有錯誤望指正
原創文章轉載引用請註明原文連結blog.wwenj.com/index.php/a…