Vue.js 最核心的功能就是組件(Component),從組件的構建、註冊到組件間通訊,Vue 2.x 提供了更多方式,讓咱們更靈活地使用組件來實現不一樣需求。
一個組件由 template、data、computed、methods等選項組成。須要注意:前端
// 示例:定義一個組件 MyComponent var MyComponent = {{ data: function () { return { // 數據 } }, template: '<div>組件內容</div>' }
因爲 HTML 特性不區分大小寫, 在使用kebab-case
(小寫短橫線分隔命名) 定義組件時,引用也須要使用這個格式如 <my-component>
來使用;在使用PascalCase
(駝峯式命名) 定義組件時<my-component>
和<MyComponent>
這兩種格式均可以引用。vue
若是項目中使用打包編譯工具 webpack,那引入 vue-loader 就可使用 .vue
後綴文件構建組件。
一個.vue
單文件組件 (SFC) 示例:webpack
// MyComponent.vue 文件 <template> <div>組件內容</div> </template> <script> export default { data () { return { // 數據 } } } </script> <style scoped> div{ color: red } </style>
.vue
文件使組件結構變得清晰,使用.vue
還須要安裝 vue-style-loader 等加載器並配置 webpack.config.js 來支持對 .vue 文件及 ES6 語法的解析。web
進一步學習可參考文章: 詳解 SFC 與 vue-loader
組件定義完後,還須要註冊纔可使用,註冊分爲全局和局部註冊:正則表達式
// 全局註冊,任何 Vue 實例均可引用 Vue.component('my-component', MyComponent) // 局部註冊,在註冊實例的做用域下有效 var MyComponent = { /* ... */ } new Vue({ components: { 'my-component': MyComponent } }) // 局部註冊,使用模塊系統,組件定義在統一文件夾中 import MyComponent from './MyComponent.vue' export default { components: { MyComponent // ES6 語法,至關於 MyComponent: MyComponent } }
注意全局註冊的行爲必須在根 Vue 實例 (經過 new Vue) 建立以前發生。vue-router
對於通用模塊使用枚舉的註冊方式代碼會很是不方便,推薦使用自動化的全局註冊。若是項目使用 webpack,就可使用其中的require.context
一次性引入組件文件夾下全部的組件:segmentfault
import Vue from 'vue' import upperFirst from 'lodash/upperFirst' // 使用 lodash 進行字符串處理 import camelCase from 'lodash/camelCase' const requireComponent = require.context( './components', // 其組件目錄的相對路徑 false, // 是否查詢其子目錄 /Base[A-Z]\w+\.(vue|js)$/ // 匹配基礎組件文件名的正則表達式 ) requireComponent.keys().forEach(fileName => { // 獲取組件配置 const componentConfig = requireComponent(fileName) // 獲取組件的 PascalCase 命名 const componentName = upperFirst( camelCase( // 剝去文件名開頭的 `./` 和結尾的擴展名 fileName.replace(/^\.\/(.*)\.\w+$/, '$1') ) ) // 全局註冊組件 Vue.component( componentName, componentConfig.default || componentConfig ) })
Vue 2.x 之後父組件用props
向子組件傳遞數據,這種傳遞是單向/正向的,反之不能。這種設計是爲了不子組件無心間修改父組件的狀態。數組
子組件須要選項props
聲明從父組件接收的數據,props
能夠是字符串數組和對象,一個 .vue 單文件組件示例以下函數
// ChildComponent.vue <template> <div> <b>子組件:</b>{{message}} </div> </template> <script> export default { name: "ChildComponent", props: ['message'] } </script>
父組件可直接傳單個數據值,也能夠可使用指令v-bind
動態綁定數據:工具
// parentComponent.vue <template> <div> <h1>父組件</h1> <ChildComponent message="父組件向子組件傳遞的非動態值"></ChildComponent> <input type="text" v-model="parentMassage"/> <ChildComponent :message="parentMassage"></ChildComponent> </div> </template> <script> import ChildComponent from '@/components/ChildComponent' export default { components: { ChildComponent }, data () { return { parentMassage: '' } } } </script>
配置路由後運行效果以下:
當子組件向父組件傳遞數據時,就要用到自定義事件。子組件中使用 $emit()
觸發自定義事件,父組件使用$on()
監聽,相似觀察者模式。
子組件$emit()
使用示例以下:
// ChildComponent.vue <template> <div> <b>子組件:</b><button @click="handleIncrease">傳遞數值給父組件</button> </div> </template> <script> export default { name: "ChildComponent", methods: { handleIncrease () { this.$emit('increase',5) } } } </script>
父組件監聽自定義事件 increase,並作出響應的示例:
// parentComponent.vue <template> <div> <h1>父組件</h1> <p>數值:{{total}}</p> <ChildComponent @increase="getTotal"></ChildComponent> </div> </template> <script> import ChildComponent from '@/components/ChildComponent' export default { components: { ChildComponent }, data () { return { total: 0 } }, methods: { getTotal (count) { this.total = count } } } </script>
訪問 parentComponent.vue 頁面,點擊按鈕後子組件將數值傳遞給父組件:
組件的關係有不少時跨級的,這些組件的調用造成多個父鏈與子鏈。父組件能夠經過this.$children
訪問它全部的子組件,可無限遞歸向下訪問至最內層的組件,同理子組件能夠經過this.$parent
訪問父組件,可無限遞歸向上訪問直到根實例。
如下是子組件經過父鏈傳值的部分示例代碼:
// parentComponent.vue <template> <div> <p>{{message}}</p> <ChildComponent></ChildComponent> </div> </template> // ChildComponent.vue <template> <div> <b>子組件:</b><button @click="handleChange">經過父鏈直接修改數據</button> </div> </template> <script> export default { name: "ChildComponent", methods: { handleChange () { this.$parent.message = '來自 ChildComponent 的內容' } } } </script>
顯然點擊父組件頁面的按鈕後會收到子組件傳過來的 message。
在業務中應儘可能避免使用父鏈或子鏈,由於這種數據依賴會使父子組件緊耦合,一個組件可能被其餘組件任意修改顯然是很差的,因此組件父子通訊經常使用props
和$emit
。
子孫的鏈式通訊顯然會使得組件緊耦合,同時兄弟組件間的通訊該如何實現呢?這裏介紹中央事件總線的方式,實際上就是用一個vue實例(Bus)做爲媒介,須要通訊的組件都引入 Bus,以後經過分別觸發和監聽 Bus 事件,進而實現組件之間的通訊和參數傳遞。
首先建 Vue 實例做爲總線:
// Bus.js import Vue from 'vue' export default new Vue;
須要通訊的組件都引入 Bus.js,使用 $emit
發送信息:
// ComponentA.vue <template> <div> <b>組件A:</b><button @click="handleBus">傳遞數值給須要的組件</button> </div> </template> <script> import Bus from './bus.js' export default { methods: { handleBus () { Bus.$emit('someBusMessage','來自ComponentA的數據') } } } </script>
須要組件A信息的就使用$on
監聽:
// ComponentB.vue <template> <div> <b>組件B:</b><button @click="handleBus">接收組件A的信息</button> <p>{{message}}</p> </div> </template> <script> import Bus from './bus.js' export default { data() { return { message: '' } }, created () { let that = this // 保存當前對象的做用域this Bus.$on('someBusMessage',function (data) { that.message = data }) }, beforeDestroy () { // 手動銷燬 $on 事件,防止屢次觸發 Bus.$off('someBusMessage', this.someBusMessage) } } </script>
組件能夠在本身的 template 模板中調用本身,須要設置 name 選擇。
// 遞歸組件 ComponentRecursion.vue <template> <div> <p>遞歸組件</p> <ComponentRecursion :count="count + 1" v-if="count < 3"></ComponentRecursion> </div> </template> <script> export default { name: "ComponentRecursion", props: { count: { type: Number, default: 1 } } } </script>
若是遞歸組件沒有 count 等限制數量,就會拋出錯誤(Uncaught RangeError: Maximum call stack size exceeded)。
父頁面使用該遞歸組件,在 Chrome 中的 Vue Devtools 能夠看到組件遞歸了三次:
遞歸組件能夠開發未知層級關係的獨立組件,如級聯選擇器和樹形控件等。
若是將一個 Vue 組件命名爲 Component 會報錯(Do not use built-in or reserved HTML elements as component id: Component),由於 Vue 提供了特殊的元素 <component>
來動態掛載不一樣的組件,並使用 is
特性來選擇要掛載的組件。
如下是使用<component>
動態掛載不一樣組件的示例:
// parentComponent.vue <template> <div> <h1>父組件</h1> <component :is="currentView"></component> <button @click = "changeToViewB">切換到B視圖</button> </div> </template> <script> import ComponentA from '@/components/ComponentA' import ComponentB from '@/components/ComponentB' export default { components: { ComponentA, ComponentB }, data() { return { currentView: ComponentA // 默認顯示組件 A } }, methods: { changeToViewB () { this.currentView = ComponentB // 切換到組件 B } } } </script>
改變 this.currentView
的值就能夠自由切換 AB 組件:
與之相似的是
vue-router
的實現原理,前端路由到不一樣的頁面實際上就是加載不一樣的組件。