咱們知道,父組件使用 prop 傳遞數據給子組件。但子組件怎麼跟父組件通訊呢?這個時候 Vue 的自定義事件系統就派得上用場了。php
每一個 Vue 實例都實現了事件接口,即:html
使用 $on(eventName) 監聽事件vue
使用 $emit(eventName) 觸發事件ios
Vue 的事件系統與瀏覽器的 EventTarget API 有所不一樣。儘管它們的運行起來相似,可是 $on 和 $emit 並非addEventListener 和 dispatchEvent 的別名。vue-router
另外,父組件能夠在使用子組件的地方直接用 v-on 來監聽子組件觸發的事件。
不能用 $on 偵聽子組件釋放的事件,而必須在模板裏直接用 v-on 綁定,參見下面的例子。npm
//定義子組件 var Child = { //獲取data()中的數據,並添加一個點擊事件 template: `<button @click="addCounter">{{counter}}</button>`, data(){ return { counter: 0 } }, methods:{ addCounter(){ this.counter++; //自定義事件$emit傳回根組件,同時調用根組件方法 this.$emit('add-parent-total') } } } //根組件 var vm = new Vue({ el: "#app", data:{ total: 0 }, components: { Child }, methods: { //根組件中子組件改變根組件的方法 addTotal(){ this.total++ } } }) //html <child @add-parent-total="addTotal"></child>
注意兩點:axios
<app> 組件不知道它會收到什麼內容。這是由使用 <app> 的父組件決定的。api
<app> 組件極可能有它本身的模板。瀏覽器
爲了讓組件能夠組合,咱們須要一種方式來混合父組件的內容與子組件本身的模板。這個過程被稱爲內容分發 (即 Angular 用戶熟知的「transclusion」)。Vue.js 實現了一個內容分發 API,參照了當前 Web Components 規範草案,使用特殊的 <slot> 元素做爲原始內容的插槽。app
//slot插槽使其能夠在html中傳入數據,也能夠在其中傳入默認內容 var Child = { template: `<div>1 <slot>默認內容</slot></div>` } var vm = new Vue({ el: "#app", components: { Child } }) html <child>hello</child>
有名slot
//slot插槽使其能夠在html中傳入數據,也能夠在其中傳入默認內容 var Child = { template: `<div>1 <slot name="header">header</slot> <slot>默認內容</slot> <slot name="footer">footer</slot> </div>` } var vm = new Vue({ el: "#app", components: { Child } }) html <child> <div slot="header">頭部內容</div> 22222 <div slot="footer">底部內容</div> </child>
有時候,非父子關係的兩個組件之間也須要通訊。在簡單的場景下,能夠使用一個空的 Vue 實例做爲事件總線:
var bus = new Vue() var AppHead = { template: '<div><button @click="add">添加<button></div>', methods: { add() { // 觸發組件A中的事件 bus.$emit('change', 1) } } } var AppBody = { template: '<div>{{ counter }}</div>', data() { return { counter: 0 } }, created() { //在組件B建立的鉤子中監聽事件 bus.$on('change', count => { this.counter += count }) } } var vm = new Vue ({ el: '#app', components: { AppHead, AppBody } })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> *{margin: 0;padding: 0;} html, body { height: 100%; } .dcj-modal { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.3); } .dcj-modal .dcj-modal-container { position: absolute; background: white; border: 1px dashed green; width: 300px; min-height: 200px; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%); } .dcj-modal .dcj-modal-container .dcj-modal-header { position: relative; line-height: 50px; text-align: center; border-bottom: 1px solid #ccc; } .dcj-modal .dcj-modal-container .dcj-modal-header button { position: absolute; right: 8px; top: 40%; transform: translateY(-50%); } .dcj-modal .dcj-modal-container .dcj-modal-body { padding: 8px; } .dcj-modal .dcj-modal-container .dcj-modal-footer { position: absolute; bottom: 0; width: 100%; height: 30px; text-align: center; line-height: 30px; border-top: 1px solid #ccc; } </style> </head> <body> <div id="app"> <button @click="openRegister">註冊</button> <button @click="openLogin">登陸</button> <modal v-show="showRegister" @close-modal="closeRegister"> <p slot="header">註冊</p> <register></register> <p slot="footer"> <input type="submit" value="註冊" form="register"/> </p> </modal> <modal v-show="showLogin" @close-modal="closeLogin"> <p slot="header">登陸</p> <login></login> <p slot="footer"> <input type="submit" value="登陸" form="login"/> </p> </modal> </div> <script src="vue.js" charset="utf-8"></script> <script> var Login = { template: `<form action="" id="login"> 用戶名: <input type="text" /><br> 密 碼: <input type="password" /><br> </form>` } var Register = { template: `<form action="" id="register"> 用戶名: <input type="text" /><br> 密 碼: <input type="password" /><br> </form>` } var Modal = { template: `<div class="dcj-modal" @click.self="closeModal"> <div class="dcj-modal-container"> <div class="dcj-modal-header"> <slot name="header">title</slot> <button @click="closeModal">關閉</button> </div> <div class="dcj-modal-body"> <slot>body</slot> </div> <div class="dcj-modal-footer"> <slot name="footer">footer</slot> </div> </div> </div>`, methods: { closeModal() { this.$emit('close-modal'); } } } var vm = new Vue({ el: '#app', data: { showRegister: false, showLogin: false }, methods: { openRegister() { this.showRegister = true }, closeRegister() { this.showRegister = false }, openLogin() { this.showLogin = true }, closeLogin() { this.showLogin = false } }, components: { Modal, Register, Login } }) </script> </body> </html>
對於大多數單頁面應用,都推薦使用官方支持的 vue-router 庫。更多細節能夠看 vue-router 文檔。
不須要複雜的路由
首先須要 npm i -S vue-router 安裝router庫 var NewsComponent = { template: `<div>新聞</div>`} var ShopComponent = { template: `<div>商場</div>`} var NotFoundComponent = { template: `<div>404</div>`} var routes = [ { path: '/', redirect: '/news'//默認根目錄跳轉 }, { path: '/news', component: NewsComponent }, { path: '/shop', component: ShopComponent }, { path: '*', component: NotFoundComponent } ] var router = new VueRouter({ routes }) // var app = new Vue({ // el: "#app", // router // }) var app = new Vue({ el: "#app", router })
var Goods = { template: `<div> <ul> <li v-for="(good, index) in goods" :key="'goods' + index"> <img :src="good.goods_thumb" :alt="good.goods_name" :title="good.goods_name"/> {{ good.goods_name }} {{ good.price }} </li> </ul> </div>`, watch: { $route (to, from) { console.log(to.params.id); axios.get('http://h6.duchengjiu.top/shop/api_goods.php?cat_id='+to.params.id).then(res => { console.log(res.data.data) this.goods = res.data.data }) } }, data() { return { goods: [] } } } var router = new VueRouter({ routes: [ { path: '/cat/:id', component: Goods } ] }); var vm = new Vue({ el: '#app', router, data: { cats: [] }, created() { axios.get('http://h6.duchengjiu.top/shop/api_cat.php').then(res => { console.log(res) this.cats = res.data.data }) } });