Vue資料通訊詳解
如果有需要原始碼,請猛戳原始碼
希望文章給大家些許幫助和啟發,麻煩大家在GitHub上面點個贊!!!十分感謝
一、前言
元件是 vue.js最強大的功能之一,而元件例項的作用域是相互獨立的,這就意味著不同元件之間的資料無法相互引用。元件間如何傳遞資料就顯得至關重要。本文儘可能羅列出一些常見的資料傳遞方式,如props、$emit
/$on
和vuex以及新出的$attrs
/$listeners
和provide/inject,以通俗易懂的例項講述這其中的差別,希望對小夥伴有些許幫助。
二、props
父元件A通過props的方式向子元件B傳遞,B to A 通過在 B 元件中 $emit, A 元件中 v-on 的方式實現。具體實現方式請點選
prop是單向繫結的,當父元件的屬性變化時,將傳導給子元件,反之則不行,
而且不允許直接在一個子元件內部改變 prop,否則就會報錯。那有時候如果我們想修改傳遞過來的prop,有以下辦法:
方式1:如果子元件想把它作為區域性資料來使用,可以將資料存入另一個變數中再操作,不影響父元件中的資料。
<div id="itany"> <h2>父元件:{{name}}</h2> <input type="text" v-model="name"> <my-hello :name="name"></my-hello> </div> <template id="hello"> <div> <h3>子元件{{username}}</h3> <button @click="change">修改資料</button> </div> </template> <script> var vm = new Vue({ //父元件 el: '#itany', data: { name: "tom" }, components: { 'my-hello': { //子元件 props: ["name"], template: '#hello', data() { return { username: this.name } }, // computed: { // changeName() { //如果要實時監測父元件資料的變化,還必須用到計算屬性, 然而計算屬性不能直接被更改 // return this.name // } // }, methods: { change() { this.username = "alice"; // this.changeName = "alice";該方法無效,不能直接更改計算屬性 } }, } } }); </script>
這種方法雖然可通過操作另一個變數,影響父元件傳遞過來的資料,但有一個很大弊端就是從此後子元件不能隨著父元件的資料變化而變化。此時如果藉助計算屬性,雖然可以同步變化,但子元件卻不能更改傳遞過來的資料。所以該方法比較少用。
方式2:如果子元件想修改資料並且同步更新到父元件,兩個方法:
a.使用.sync(1.0版本中支援,2.0版本中不支援,2.3版本又開始支援)需要顯式地觸發一個更新事件。
<div id="itany"> <h2>父元件:{{name}}</h2> <input type="text" v-model="name"> <hr> <my-hello :name.sync="name" :user="user"></my-hello> </div> <template id="hello"> <div> <h3>子元件:{{name}}</h3> <button @click="change">修改資料</button> </div> </template> <script> var vm = new Vue({ //父元件 el: '#itany', data: { name: 'tom' }, components: { 'my-hello': { //子元件 template: '#hello', props: ['name'], methods: { change() { // this.name='alice';這種寫法不行 this.$emit('update:name', 'alice123'); //方式2:a.使用.sync,需要顯式地觸發一個更新事件 } } } } }); </script>
b.可以將父元件中的資料包裝成物件,然後在子元件中修改物件的屬性(因為物件是引用型別,指向同一個記憶體空間),推薦
<div id="itany">
<h2>父元件:{{name}}</h2>
<input type="text" v-model="name">
<h2>父元件:{{user.age}}</h2>
<hr>
<my-hello :name.sync="name" :user="user"></my-hello>
</div>
<template id="hello">
<div>
<h3>子元件:{{name}}</h3>
<h3>子元件:{{user.age}}</h3>
<button @click="change">修改資料</button>
</div>
</template>
<script>
var vm = new Vue({ //父元件
el: '#itany',
data: {
name: 'tom',
user: { //父元件中的資料包裝成物件
name: 'zhangsan',
age: 24
}
},
components: {
'my-hello': { //子元件
template: '#hello',
props: ['name', 'user'],
methods: {
change() {
this.user.age = 18;
}
}
}
}
});
</script>
這是因為在 JavaScript 中物件和陣列是通過引用傳入的,所以對於一個數組或物件型別的 prop 來說,在子元件中改變這個物件或陣列本身將會影響到父元件的狀態。
三、$emit
/$on
這種方法通過一個空的Vue例項作為中央事件匯流排(事件中心),用它來觸發事件和監聽事件,巧妙而輕量地實現了任何元件間的通訊,包括父子、兄弟、跨級。當我們的專案比較大時,可以選擇更好的狀態管理解決方案vuex。
1.具體實現方式:
var Event=new Vue();
Event.$emit(事件名,資料);
Event.$on(事件名,data => {});
2.舉個例子:兄弟元件有三個,分別是A、B、C,C元件如何獲取A或者B元件的資料
<div id="itany">
<my-a></my-a>
<my-b></my-b>
<my-c></my-c>
</div>
<template id="a">
<div>
<h3>A元件:{{name}}</h3>
<button @click="send">將資料傳送給C元件</button>
</div>
</template>
<template id="b">
<div>
<h3>B元件:{{age}}</h3>
<button @click="send">將陣列傳送給C元件</button>
</div>
</template>
<template id="c">
<div>
<h3>C元件:{{name}},{{age}}</h3>
</div>
</template>
<script>
var Event = new Vue();//定義一個空的Vue例項
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
Event.$emit('data-a', this.name);
}
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
Event.$emit('data-b', this.age);
}
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() {//在模板編譯完成後執行
Event.$on('data-a',name => {
this.name = name;//箭頭函式內部不會產生新的this,這邊如果不用=>,this指代Event
})
Event.$on('data-b',age => {
this.age = age;
})
}
}
var vm = new Vue({
el: '#itany',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}
});
</script>
四、vuex
1.簡要介紹Vuex原理
Vuex實現了一個單向資料流,在全域性擁有一個State存放資料,當元件要更改State中的資料時,必須通過Mutation進行,Mutation同時提供了訂閱者模式供外部外掛呼叫獲取State資料的更新。而當所有非同步操作(常見於呼叫後端介面非同步獲取更新資料)或批量的同步操作需要走Action,但Action也是無法直接修改State的,還是需要通過Mutation來修改State的資料。最後,根據State的變化,渲染到檢視上。
2.簡要介紹各模組在流程中的主要功能:
- Vue Components:Vue元件。HTML頁面上,負責接收使用者操作等互動行為,執行dispatch方法觸發對應action進行迴應。
- dispatch:操作行為觸發方法,是唯一能執行action的方法。
- actions:操作行為處理模組,由元件中的
$store.dispatch('action 名稱', data1)
來觸發。然後由commit()來觸發mutation的呼叫 , 間接更新 state。負責處理Vue Components接收到的所有互動行為。包含同步/非同步操作,支援多個同名方法,按照註冊的順序依次觸發。向後臺API請求的操作就在這個模組中進行,包括觸發其他action以及提交mutation的操作。該模組提供了Promise的封裝,以支援action的鏈式觸發。 - commit:狀態改變提交操作方法。對mutation進行提交,是唯一能執行mutation的方法。
- mutations:狀態改變操作方法,由actions中的
commit('mutation 名稱')
來觸發。是Vuex修改state的唯一推薦方法。該方法只能進行同步操作,且方法名只能全域性唯一。操作之中會有一些hook暴露出來,以進行state的監控等。 - state:頁面狀態管理容器物件。集中儲存Vue components中data物件的零散資料,全域性唯一,以進行統一的狀態管理。頁面顯示所需的資料從該物件中進行讀取,利用Vue的細粒度資料響應機制來進行高效的狀態更新。
- getters:state物件讀取方法。圖中沒有單獨列出該模組,應該被包含在了render中,Vue Components通過該方法讀取全域性state物件。
如果你想深入瞭解,請點選從頭開始學習Vuex這篇文章
五、localStorage
1.簡介
HTML5中新增了本地儲存的解決方案----WebStorage,它分成兩類:sessionStorage和localStorage。localStorage儲存的資料長期存在,除非被清除,下一次訪問該網站的時候,網頁可以直接讀取以前儲存的資料。
localStorage儲存的資料,以“鍵值對”的形式存在。也就是說,每一項資料都有一個鍵名和對應的值。所有的資料都是以文字格式儲存。
存入資料使用setItem方法。它接受兩個引數,第一個是鍵名,第二個是儲存的資料。localStorage.setItem("key","value");
讀取資料使用getItem方法。它只有一個引數,就是鍵名。var valueLocal = localStorage.getItem("key");
如果想深入瞭解,請點選瀏覽器儲存這篇文章
2.localStorage與Vuex區別
vuex 是 vue 的狀態管理器,儲存的資料是響應式的。但是並不會儲存起來,重新整理之後就回到了初始狀態,具體做法應該在vuex裡資料改變的時候把資料拷貝一份儲存到localStorage裡面,重新整理之後,如果localStorage裡有儲存的資料,取出來再替換store裡的state。
let defaultCity = "上海"
try { // 使用者關閉了本地儲存功能,此時在外層加個try...catch
if (!defaultCity){
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
}
}catch(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) {}
}
}
})
3.注意點
由於vuex裡,我們儲存的狀態,都是陣列,而localStorage只支援字串,所以需要用JSON轉換:
JSON.stringify(state.subscribeList); // array -> string
JSON.parse(window.localStorage.getItem("subscribeList")); // string -> array
六、$attrs
/$listeners
1.簡介
多級元件巢狀需要傳遞資料時,通常使用的方法是通過vuex。但如果僅僅是傳遞資料,而不做中間處理,使用 vuex 處理,未免有點大材小用。為此Vue2.4 版本提供了另一種方法,當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部元件。通常配合 interitAttrs 選項一起使用。
// demo.vue
<template>
<div>
<child-com:foo="foo":boo="boo":coo="coo":doo="doo"></child-com>
</div>
</tempalte>
<script>
const childCom = ()=> import('./childCom1.vue')
export default {
data () {
return {
foo: 'Hello World!',
boo: 'Hello Javascript!',
coo: 'Hello Vue',
doo: 'Last'
}
},
components: { childCom }
}
</script>
// childCom1.vue
<template>
<div>
<p>foo: {{ foo }}</p>
<p>attrs: {{ $attrs }}</p>
<child-com2 v-bind="$attrs"></child-com2>
</div>
</template>
<script>
const childCom2 = ()=> import('./childCom2.vue')
export default {
props: ['foo'], // foo作為props屬性繫結
inheritAttrs: false,
created () {
console.log(this.$attrs) // { boo: 'Hello Javascript!', coo: 'Hello Vue', doo: 'Last' }
}
}
</script>
// childCom2.vue
<template>
<div>
<p>boo: {{ boo }}</p>
<p>attrs: {{ $attrs }}</p>
<child-com3 v-bind="$attrs"></child-com3>
</div>
</template>
<script>
const childCom3 = ()=> import('./childCom3.vue')
export default {
props: ['boo'] // boo作為props屬性繫結
inheritAttrs: false,
created () {
console.log(this.$attrs) // { coo: 'Hello Vue', doo: 'Last' }
}
}
</script>
$attrs
表示沒有繼承資料的物件,格式為{屬性名:屬性值}。Vue2.4提供了$attrs
, $listeners
來傳遞資料與事件,跨級元件之間的通訊變得更簡單
七、provide/inject
1.簡介
Vue2.2.0新增API,這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效。一言而蔽之:祖先元件中通過provider來提供變數,然後在子孫元件中通過inject來注入變數。
2.舉個例子
// 父元件
export default {
name: "Parent",
provide: {
parent: "父元件的值"
}
}
// 子元件
export default {
name: "",
inject: ['parent'],
data() {
return {
demo: this.parent //"父元件的值"
}
}
}
上例中子元件中inject注入了父元件provide提供的變數parent,並將它提供給了data屬性