組件(Component)是 Vue.js 最強大的功能之一,組件能夠擴展 HTML 元素,封裝可重用的代碼。html
組件:爲了拆分Vue實例的代碼量,以不一樣的組件來劃分不一樣的功能模塊,須要什麼樣的功能,能夠去調用對應的組件。前端
模塊化和組件化的區別:vue
◊ 模塊化:是從代碼邏輯的角度進行劃分的;方便代碼分層開發,保證每一個功能模塊的職能單一。node
◊ 組件化:是從UI界面的角度進行劃分的;前端的組件化,方便UI組件的重用。 數組
Vue.js提供兩種組件註冊方式:全局註冊和局部註冊。app
全局註冊須要在根實例初始化以前註冊,這樣組件才能在任意實例中被使用。dom
註冊全局組件語法格式:ide
Vue.component(tagName, options)
其中,tagName 爲組件名,options 爲配置選項。模塊化
這條語句須要寫在var vm = new Vue({ options })以前。函數
註冊組件後調用方式:
<tagName></tagName>
全部實例都能用全局組件。
組件名定義方式:PascalCase和kebab-case。在組件命名時能夠採用PascalCase或kebab-case,但在DOM中只能使用kebab-case。
PascalCase示例:
<div id="app"> <my-component></my-component> </div> <script> Vue.component('MyComponent', { template: '<div>標題</div>' }); var vm = new Vue({ el: "#app" }); </script>
kebab-case示例:
<div id="app"> <my-component></my-component> </div> <script> Vue.component('my-component', { template: '<div>標題</div>' }); var vm = new Vue({ el: "#app" }); </script>
<div id="app"> <home></home> </div> <script> Vue.component("home", { template: "<div>{{text}}</div>", data: function () { return { text: "主頁" }; } }); new Vue({ el: "#app" }); </script>
<div id="app"> <home></home> </div> <script> var homeTpl = Vue.extend({ template: "<div>{{text}}</div>", data: function () { return { text: "主頁" }; } }); Vue.component('home', homeTpl); new Vue({ el: "#app" }); </script>
使用template標籤:
<div id="app"> <home></home> </div> <template id="tpl"> <div>{{text}}</div> </template> <script> Vue.component("home", { template: "#tpl", data: function () { return { text: "主頁" }; } }); new Vue({ el: "#app" }); </script>
局部組件只能在被註冊的組件中使用,不能在其餘組件中使用。
<div id="app"> <home></home> </div> <script> new Vue({ el: "#app", components: { "home": { template: "<div>{{text}}</div>", data: function () { return { text: "主頁" }; } } } }); </script>
<div id="app"> <home></home> </div> <script> var home = Vue.extend({ template: "<div>標題</div>" }); Vue.component("home", home); new Vue({ el: "#app" }); </script>
data:在 Vue.extend() 中必須是函數。
<body> <task></task> <script> var task = Vue.extend({ template:"<div>{{ taskName }}</div>", data:function(){ return { taskName:"任務名稱" } } }); new task().$mount("task"); </script> </body>
在實例中沒有el選項時,可經過mount掛載。
mount:掛載,將vue實例掛靠在某個dom元素上的一個過程。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>libing.vue</title> <script src="node_modules/vue/dist/vue.min.js"></script> </head> <body> <div id="app"></div> <script> var home = Vue.extend({ template: "<div>標題</div>" }); new home().$mount("#app"); </script> </body> </html>
prop 是組件用來傳遞數據的自定義特性,在組件上註冊自定義屬性。
prop特性註冊成爲組件實例的屬性。
props
:父組件向子組件傳遞數據。
一個組件默承認以擁有任意數量的 prop,任何值均可以傳遞給任何 prop。
示例:
<div id="app"> <home text="主頁"></home> </div> <script> var homeTpl = Vue.extend({ props:["text"], template: "<div>{{text}}</div>" }); Vue.component('home', homeTpl); new Vue({ el: "#app" }); </script>
使用 v-bind 動態綁定 props 的值到父組件的數據中。每當父組件的數據變化時,該變化也會傳導給子組件。
<div id="app"> <home v-bind:text="text"></home> </div> <script> var homeTpl = Vue.extend({ props: ["text"], template: "<div>{{text}}</div>" }); Vue.component('home', homeTpl); new Vue({ el: "#app", data: { text: "主頁" } }); </script>
因爲HTML Attribute不區分大小寫,當使用DOM模板時,camelCase的props名稱要轉爲kebab-case。
<div id="app"> <home warning-text="提示信息"></home> </div> <script> Vue.component('home', { props: ['warningText'], template: '<div>{{ warningText }}</div>' }); var vm = new Vue({ el: "#app" }); </script>
傳遞的數據能夠是來自父級的動態數據,使用指令v-bind來動態綁定props的值,當父組件的數據變化時,也會傳遞給子組件。
<div id="app"> <home v-bind:warning-text="warningText"></home> </div> <script> Vue.component('home', { props: ['warningText'], template: '<div>{{ warningText }}</div>' }); var vm = new Vue({ el: "#app", data: { warningText: '提示信息' } }); </script>
注:prop 是單向傳遞,當父組件的屬性變化時,將傳遞給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態。
示例:
<template> <li>{{ id }}-{{ text }}</li> </template> <script> export default { name: "TodoItem", props: ["id", "text"] }; </script>
<template> <ul> <TodoItem v-for="item in list" :key="item.id" :id="item.id" :text="item.text" ></TodoItem> </ul> </template> <script> import TodoItem from "./TodoItem"; export default { name: "TodoList", components: { TodoItem }, data: function() { return { list: [ { id: 1, text: "To Do" }, { id: 2, text: "In progress" }, { id: 3, text: "Done" } ] }; } }; </script>
<template> <div id="app"> <TodoList /> </div> </template> <script> import TodoList from './views/TodoList' export default { name: 'App', components: { TodoList } } </script>
爲組件的 prop 指定驗證要求,若是有一個需求沒有被知足,則 Vue 會在控制檯中警告。
Vue.component('my-component', { props: { // 基礎的類型檢查 (`null` 匹配任何類型) propA: Number, // 多個可能的類型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 帶有默認值的數字 propD: { type: Number, default: 100 }, // 帶有默認值的對象 propE: { type: Object, // 對象或數組且必定會從一個工廠函數返回默認值 default: function () { return { message: 'hello' } } }, // 自定義驗證函數 propF: { validator: function (value) { // 這個值必須匹配下列字符串中的一個 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } });
類型檢查:type能夠是下列原生構造函數中的一個:String、Number、Boolean、Array、Object、Date、Function、Symbol,也能夠是一個自定義的構造函數,而且經過 instanceof 來進行檢查確認。
示例:
<div id="app"> <parent-component></parent-component> </div> <template id="child-component1"> <h2>{{ message }}</h2> </template> <template id="child-component2"> <h2>{{ message }}</h2> </template> <template id="parent-component"> <div> <child-component1></child-component1> <child-component2></child-component2> </div> </template> <script> Vue.component('parent-component', { template: '#parent-component', components: { 'child-component1': { template: '#child-component1', data() { return { message: '子組件1' }; } }, 'child-component2': { template: '#child-component2', data() { return { message: '子組件2' }; } } } }); var vm = new Vue({ el: "#app" }); </script>
示例:
<div id="app"> <todo :todo-data="taskList"></todo> </div> <template id="tpl-todo-item"> <li>{{ id }} - {{ text }}</li> </template> <template id="tpl-todo-list"> <ul> <todo-item v-for="item in todoData" :id="item.id" :text="item.text"></todo-item> </ul> </template> <script> // 構建一個子組件 var todoItem = Vue.extend({ template: "#tpl-todo-item", props: { id: { type: Number, required: true }, text: { type: String, default: '' } } }) // 構建一個父組件 var todoList = Vue.extend({ template: "#tpl-todo-list", props: { todoData: { type: Array, default: [] } }, // 局部註冊子組件 components: { todoItem: todoItem } }) // 註冊到全局 Vue.component('todo', todoList) new Vue({ el: "#app", data: { taskList: [{ id: 1, text: 'New' }, { id: 2, text: 'InProcedure' }, { id: 3, text: 'Done' } ] } }); </script>
每個Vue實例都實現事件接口:
$on(eventName)
:監聽事件
$emit(eventName)
:觸發事件,自定義事件。推薦始終使用 kebab-case 的事件名。
子組件須要向父組件傳遞數據時,子組件用$emit(eventName)
來觸發事件,父組件用$on(eventName)
來監聽子組件的事件。
示例1:
<template> <div> <button @click="onparent">子組件觸發父組件</button> </div> </template> <script> export default { methods: { onparent() { this.$emit("onchild"); } } }; </script>
<template> <div> <Child @onchild="inparent"></Child> </div> </template> <script> import Child from "./Child"; export default { components: { Child }, methods: { inparent() { console.log("父組件響應了"); } } }; </script>
<template> <div id="app"> <Parent /> </div> </template> <script> import Parent from './views/Parent' export default { name: 'App', components: { Parent } } </script>
示例2:
<div id="app"> <searchbar></searchbar> </div> <template id="tpl-search-form"> <div class="input-group form-group" style="width: 500px;"> <input type="text" class="form-control" placeholder="請輸入查詢關鍵字" v-model="keyword" /> <span class="input-group-btn"> <input type="button" class="btn btn-primary" value="查詢" @click="search"> </span> </div> </template> <template id="tpl-search-bar"> <searchform @onsearch="search"></searchform> </template> <script> // 構建一個子組件 var searchform = Vue.extend({ template: "#tpl-search-form", data: function () { return { keyword: 'libing' }; }, methods: { search: function () { this.$emit('onsearch', this.keyword); } } }); // 構建一個父組件 var searchbar = Vue.extend({ template: "#tpl-search-bar", components: { searchform: searchform }, methods: { search(keyword) { console.log(keyword); } } }) // 註冊到全局 Vue.component('searchbar', searchbar); new Vue({ el: "#app" }); </script>
購物車示例:
<div id="app"> <shoppingcart :shopppingcarts="products" @calc="getTotal"></shoppingcart> <div>總計:{{ totalPrice }}</div> </div> <template id="shoppingcart"> <table> <tr> <th>商品ID</th> <th>商品名稱</th> <th>單價</th> <th>數量</th> </tr> <tr v-for="item in shopppingcarts"> <td>{{ item.ID }}</td> <td>{{ item.ProductName }}</td> <td>{{ item.UnitPrice }}</td> <td><input type="text" v-model="item.Quantity" @change="calcTotal" /></td> </tr> </table> </template> <script> var shoppingcart = Vue.extend({ template: "#shoppingcart", props: ["shopppingcarts"], methods: { calcTotal: function () { this.$emit("calc"); } } }); new Vue({ el: "#app", components: { shoppingcart: shoppingcart }, data: { totalPrice: 100, products: [{ ID: 1, ProductName: "手機", UnitPrice: 1000, Quantity: 2 }, { ID: 2, ProductName: "電腦", UnitPrice: 5000, Quantity: 5 }] }, methods: { getTotal() { console.log(new Date()); this.totalPrice = 0; this.products.forEach(product => { this.totalPrice += product.UnitPrice * product.Quantity; }); } }, mounted() { //當vue執行完畢以後,去執行函數 this.getTotal(); } }); </script>
非父子組件包括:兄弟組件、跨級組件。
經過實例化一個Vue對象 (如:const bus = new Vue() ) 做爲總線,在組件中經過事件傳遞參數( bus.$emit(event, [...args]) ),再在其餘組件中經過bus來監聽此事件並接受參數( bus.$on(event, callback) ),從而實現通訊。
示例:
bus.js
import Vue from 'vue' const bus = new Vue(); export default bus;
Send.vue
<template> <div class="send"> <h1>發送參數:{{msg}}</h1> <button @click="send">發送</button> </div> </template> <script> import bus from "../utils/bus.js"; export default { data() { return { msg: "Hello World" }; }, methods: { send() { bus.$emit("receive", this.msg); } } }; </script>
Receive.vue
<template> <div class="receive"> <h1>接收參數:{{msg}}</h1> </div> </template> <script> import bus from "../utils/bus.js"; export default { data() { return { msg: "Hello" }; }, created() { bus.$on("receive", param => { this.msg = param; }); }, beforeDestroy() { bus.$off("receive"); } }; </script>
App.vue
<template> <div id="app"> <Send></Send> <Receive></Receive> </div> </template> <script> import Send from './views/Send' import Receive from './views/Receive' export default { name: 'App', components: { Send, Receive } } </script>