今天咱們看一下組件通訊。html
通過前面幾篇文章,咱們已經能夠構建出完整的單個組件,並利用路由使其串聯起來訪問了。vue
但這明顯仍是不夠的。一個頁面不可能就是個單組件,通常是由多個組件合成的。正由於如此,組件之間確定是有相互關係的,咱們就稱這種現象叫組件通訊。ajax
好比父組件發生了某項改變,子組件會跟着相應發生變化;反過來,子組件有了某種改變,父組件有時也會隨之作出調整。那麼這種現象咱們稱之爲雙向數據流動。vue-router
然而,vue的做者敏銳的認識到,雙向數據流帶來便捷的同時,也存在着極大的安全隱患。npm
父組件將變化傳遞給子組件(父影響子),這是沒問題的,在咱們平常生活中也是極爲廣泛的現象(老子教訓兒子是天經地義的),然而反過來子影響父的話,這就變得不可理喻了(兒子教訓老子?)json
子組件修改父組件,增大了組件之間的耦合度。有時開發者根本沒有意識到這種修改,這猶如埋下一顆定時炸彈,隨着往後項目代碼的膨脹,一旦引爆,問題排查難度也會呈指數級的徒增。數組
爲了斬斷這種安全隱患,vue提倡的是單向數據流動——也就是隻能父影響子,而反過來則不成立。安全
父傳子,咱們利用props做爲橋樑。下面看一個例子。異步
首先我將目錄調整一下:函數
其中,helloworld.vue組件,咱們設定其爲父組件;而children目錄下的child.vue,咱們設定爲其子組件。
Helloworld.vue代碼以下:
<!--模板部分--> <template> <div class="container"> <h1>我是父親</h1> <!--綁定父組件的某個變量myMsg--> <div> <!--注意子組件接受變量時,須要變爲駝峯命令法sendMessage--> <child v-bind:send-message="myMsg"></child> </div> </div> </template> <!--js部分--> <script> //引入子組件,須要設定子組件的name爲child import child from './children/child.vue' export default { name:'helloworld', data(){ return { myMsg:'hello,my son!' }; }, components:{child}//局部註冊子組件 } </script> <!--樣式部分--> <style> .container{ background: #ccc; color:greenyellow; } </style>
你們注意下別把組件和路由的概念搞混了,兩者之間一毛錢關係都沒有。要使用組件,需作局部或者全局註冊。我這裏只作局部註冊,你們仔細看一下寫法。
咱們在父組件定義了一個myMsg這個變量,而後經過v-bind綁定到了child組件中。這時,child組件就能夠經過props來接收這個變量了。
child.vue代碼以下:
<!--模板部分--> <template> <div class="sub-container"> <h3>我是兒子</h3> <div>我從父親那邊接收過來的信息:</div> <div>{{sendMessage}}</div> </div> </template> <!--js部分--> <script> //子組件 export default { name:'child',//必須設定name,不然沒法在父組件import props:['sendMessage'], data(){ return { }; } } </script> <!--樣式部分--> <style> .sub-container{ background: blue; color:red; } </style>
一個簡單的父子通訊案例就完成了,看一下路由router/index.js,代碼以下:
import Vue from 'vue' import Router from 'vue-router' import Hello from '@/components/Hello' import HelloWorld from '@/components/Helloworld' //咱們新定義的組件 Vue.use(Router) export default new Router({ routes: [{ path: '/', name: 'Hello', component: Hello }, { //新路由 path: '/helloworld/:id', name: 'HelloWorld', component: HelloWorld, } ] })
運行一下npm run dev,看一下結果:
能夠看到,子組件順利接收到了父組件傳來的信息。
那麼,到底子組件可否影響父組件呢?咱們將以上代碼作一下調整。
調整後的Helloworld.vue
<!--模板部分--> <template> <div class="container"> <h1>我是父親</h1> <input type="text" v-model="myMsg"> <br> <!--綁定父組件的某個變量myMsg--> <div> <!--注意子組件接受變量時,須要變爲駝峯命令法sendMessage--> <child v-bind:send-message="myMsg"></child> </div> </div> </template> <!--js部分--> <script> //引入子組件,須要設定子組件的name爲child import child from './children/child.vue' export default { name:'helloworld', data(){ return { myMsg:'hello,my son!' }; }, components:{child}//局部註冊子組件 } </script> <!--樣式部分--> <style> .container{ background: #ccc; color:greenyellow; } </style>
調整後的child.vue
<!--模板部分--> <template> <div class="sub-container"> <h3>我是兒子</h3> <div>我從父親那邊接收過來的信息:</div> <div>{{sendMessage}}</div> <div>是否能夠子影響父呢?</div> <input type="text" v-model="sendMessage"> </div> </template> <!--js部分--> <script> //子組件 export default { name:'child',//必須設定name,不然沒法在父組件import props:['sendMessage'], data(){ return { }; } } </script> <!--樣式部分--> <style> .sub-container{ background: blue; color:red; } </style>
運行一下npm run dev,看一下結果:
能夠看出,子組件是沒法影響父組件的,這樣父組件就成功解耦了。
你們要注意一個問題——傳遞的數據類型問題。
傳遞的是值類型確定沒問題,假如是對象或者數組這樣的引用類型,變量共用同一內存,因此父子會出現雙向影響,這個問題必定要注意。
爲了杜絕隱患,提升安全性,咱們在子組件將prop接收的類型作一下強制檢測,假如傳入的數據類型並非我須要的,能夠拋出異常。
Helloworld.vue
<!--模板部分--> <template> <div class="container"> <h1>我是父親</h1> <input type="text" v-model="myMsg"> <!--<input type="text" v-model="myNum">--> <ul v-for="item of myObj"> <li>{{item.name}}</li> </ul> <br> <!--綁定父組件的某個變量myMsg--> <div> <!--注意子組件接受變量時,須要變爲駝峯命令法sendMessage--> <child :send-message="myMsg" :send-num="myNum" :send-obj="myObj"></child> </div> </div> </template> <!--js部分--> <script> //引入子組件,須要設定子組件的name爲child import child from './children/child.vue' export default { name:'helloworld', data(){ return { myMsg:'hello,my son!', myNum:'123', myObj:[{id:1,name:'Tom_Lo'},{id:2,name:'tom'}] }; }, components:{child}//局部註冊子組件 } </script> <!--樣式部分--> <style> .container{ background: #ccc; color:green; } </style>
父組件此次傳了三個不一樣類型的信息,分別是字符串、數字和json對象。其中,數字類型我故意寫錯了,看一會兒組件是否會檢測出來。
child.vue
<!--模板部分--> <template> <div class="sub-container"> <h3>我是兒子</h3> <div>我從父親那邊接收過來的信息:</div> <div>字符串:{{sendMessage}}</div> <div>數字:{{sendNum}}</div> <div> <ul v-for="item of list"> <li>{{item.name}}</li> </ul> </div> </div> </template> <!--js部分--> <script> //子組件 export default { name:'child',//必須設定name,不然沒法在父組件import props:{ sendMessage:{ type:String,//傳入類型必須是字符串 required:true//必傳 }, sendNum:{ type:Number,//傳入類型必須是數字 required:true }, //若是是數組或對象 sendObj:{ validator:function(val){ if(Object.prototype.toString.call(val) === '[object Array]' || Object.prototype.toString.call(val) === '[object Object]'){ return true; } } } },//聲明驗證類型 data(){ var parentObj = this.sendObj;//獲取父組件傳來的json var childObj =JSON.parse(JSON.stringify(parentObj));//複製一份,避免污染父組件的數據 childObj.push({id:3,name:'mike'});//追加一個對象 return { list:childObj }; } } </script> <!--樣式部分--> <style> .sub-container{ background: blue; color:red; } </style>
你們須要注意下子組件props的驗證規則。
那麼對於容易引起錯誤的引用類型,你們應該如何避免呢?
當咱們接收過來這個對象數據後,先將其拷貝一份副本,而後用副本作增長修改等操做,這樣便不會影響到父組件的數據了。
咱們運行一下看看:
看起來驗證規則生效了哈~~
那麼以上都是同步的狀況,那麼異步呢?
假設父組件的數據是異步ajax獲取的,而後渲染頁面。那麼此時,子組件如何跟着更新視圖呢?
這就得有個監聽機制。當子組件監聽到父組件的數據到位後,將本地數據更新便可。
vue提供的watch能夠達到此目的,咱們看一下例子。
Helloworld.vue
<!--模板部分--> <template> <div class="container"> <h1>我是父親</h1> <input type="text" v-model="myMsg"> <!--<input type="text" v-model="myNum">--> <ul v-for="item of myObj"> <li>{{item.name}}</li> </ul> <br> <!--綁定父組件的某個變量myMsg--> <div> <!--注意子組件接受變量時,須要變爲駝峯命令法sendMessage--> <child :send-message="myMsg" :send-num="myNum" :send-obj="myObj"></child> </div> </div> </template> <!--js部分--> <script> //引入子組件,須要設定子組件的name爲child import child from './children/child.vue' export default { name:'helloworld', data(){ //初始化obj var obj = { myMsg:'hello,my son!', myNum:123, myObj:[] }; //延時返回myObj,模擬ajax的延時狀況 setTimeout(function(){ obj['myObj'] = [{id:1,name:'jack'},{id:2,name:'tom'}]; },2000); return obj; }, components:{child}//局部註冊子組件 } </script> <!--樣式部分--> <style> .container{ background: #ccc; color:green; } </style>
child.vue
<!--模板部分--> <template> <div class="sub-container"> <h3>我是兒子</h3> <div>我從父親那邊接收過來的信息:</div> <div>字符串:{{sendMessage}}</div> <div>數字:{{sendNum}}</div> <div> <ul v-for="item of list"> <li>{{item.name}}</li> </ul> </div> </div> </template> <!--js部分--> <script> //子組件 export default { name:'child',//必須設定name,不然沒法在父組件import props:{ sendMessage:{ type:String,//傳入類型必須是字符串 required:true//必傳 }, sendNum:{ type:Number,//傳入類型必須是數字 required:true }, //若是是數組或對象 sendObj:{ validator:function(val){ if(Object.prototype.toString.call(val) === '[object Array]' || Object.prototype.toString.call(val) === '[object Object]'){ return true; } } } },//聲明驗證類型 data(){ return { list:null }; }, watch:{//監控父組件sendObj的變化 sendObj(newval,oldval){ if(newval !== oldval){ var newobj =JSON.parse(JSON.stringify(newval));//複製一份,避免污染父組件的數據 newobj.push({id:3,name:'mike'});//追加一個對象 this.list = newobj;//將新值賦予data的list } } } } </script> <!--樣式部分--> <style> .sub-container{ background: blue; color:red; } </style>
watch的函數名稱,就是要監聽的值,這裏咱們監聽父組件傳來的prop;而newval和oldval參數,分別表明了新值和舊值。
在父組件未更新以前,子組件監聽到的sendObj,新值和舊值都是空數組,兩者是相等的;而當監聽到兩者發生不等時,就說明父組件傳來的信息發生了改變。咱們取新值,並將拷貝後的數據傳給data裏面的list,而後更新到視圖。
看一下運行狀況:
父組件與子組件的交互就實現了。