上一篇:vue生命週期深刻
下一篇:Vue組件通訊深刻Vuexjavascript
建議:博客中的例子都放在vue_blog_project工程中,推薦結合工程實例與博客一同窗習css
vue中,組件是帶有一個名字、可複用的 Vue 實例。因爲 Vue 是面向視圖的MVVM框架, 組件能夠看作是對數據和方法的簡單封裝、具備獨立的邏輯和功能的界面,多個組件按照必定規則的組合最終成爲一個完整的應用
Vue.component()
用來建立全局組件,一旦註冊,便可在該實例Vue下的任何子組件中使用,經常使用於一些使用較爲頻繁的基礎組件,如Alert組件、Button組件、佈局組件等html
使用方式:vue
Vue.component('my-component', { // vue實例方法和生命週期(el除外) })
若是你使用過 element-ui
,下面的寫法你可能比較熟悉:java
import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; Vue.use(ElementUI); new Vue({ el: '#app', render: h => h(App) });
其中Vue.use(ElementUI);
的方式即是間接調用了全局組件註冊的方式,在element-ui
內部:
(插件中,使用Vue.use()
的方式,至關於調用了其中的install方法)webpack
const install = function(Vue, opts = {}) { // ... components.map(component => { Vue.component(component.name, component); }); // ... };
能夠看出,在其內部也是依次全局註冊了element
中的插件git
Vue官網上如是說:github
全局註冊每每是不夠理想的。好比,若是你使用一個像 webpack 這樣的構建系統,全局註冊全部的組件意味着即使你已經再也不使用一個組件了,它仍然會被包含在你最終的構建結果中。這形成了用戶下載的 JavaScript 的無謂的增長。
正是由於上面的緣由,除了一些經常使用的基礎組件外,儘量的使用局部註冊的方式web
// 普通引入方式 var ComponentA = { /* ... */ } // ES6引入方式 import ComponentA from './ComponentA.vue' export default { // ... components: { 'component-a': ComponentA, } }
值得注意的是:局部註冊方式僅能在當前組件中使用,在其子組件中使用須要再次註冊element-ui
上面提到,多個組件按照必定規則的組合最終成爲一個完整的應用
,所以,咱們能夠將組件看做是Vue頁面中的最小單元,那麼應該如何組織組件,整合成一個頁面呢?
有這樣一個需求:要求按照下圖組織頁面結構
咱們能夠這樣組織:
<template> <div class="m-body"> 我是主體內容 </div> </template> <script> export default {} </script> <style scoped> .m-body { min-height: 500px; color: #fff; padding: 20px; background-color: #39f; } </style>
按照這種方式,依次寫出header、aside、content、footer四個組件,並用一個組件做爲這四個組件的父組件來組織頁面結構,最後的結構以下:
父組件以下:
<template> <div class="comp"> <m-header /> <div class="main"> <m-side /> <m-body /> </div> <m-footer /> </div> </template> <script> import MHeader from './MHeader' import MFooter from './MFooter' import MBody from './MBody' import MSide from './MSide' export default { components: { MHeader, MFooter, MBody, MSide } } </script> <style lang="scss" scoped> .main { margin: 10px 0; display: flex; .m-side { width: 200px; margin-right: 10px; } .m-body { flex: 1; } } </style>
打開Vue調試界面,將看到以下的結構
注意:父組件負責控制容器結構樣式(各個直接子組件的位置、大小等),子組件負責其內部的樣式,不要在子組件中寫本身的容器樣式
組件的組合僅僅只是將頁面結構搭建了起來,要完成頁面的交互功能,組件之間一定會有數據傳遞
按照頁面結構,大致上能夠將組件間的數據傳遞分紅兩種:
Vue官網中對props、$emit、slot有很是詳細的描述,在此再也不嘮述
現有新的 需求:在上面例子的基礎上,須要知足:header中有一個數值,side中新增重置和增長按鈕,body中新增數組輸入框,當對按鈕和表單做操做時,對應的數值做相應改變
基本思路:將數值放在幾個組件公共上層組件中,header中prop接受該值,side和body中點擊按鈕向他們的公共上層組件分發$emit事件,改變該數值,核心思路:多個組件操做的值均爲上層組件的變量
代碼以下:
(1)父級組件:主要用於數據傳遞與接收子組件分發的事件來改變對應的變量
<div class="comp"> <m-header :num="num" /> <div class="main"> <m-side @add="handleAdd" @reset="handleReset" /> <m-body :num="num" @change="handleChange" /> </div> <m-footer /> </div>
export default { data () { return { num: 0 } }, methods: { handleAdd () { this.num += 1 }, handleChange (val) { this.num = val }, handleReset () { this.num = 0 } }, // ... }
(2)Header組件:接受並展現數值
template中僅添加{{ num }}
props: { num: { type: Number, default: 0 } }
(3)Side組件:向上分發增長和重置事件
<!-- 新增 --> <el-button @click="add">ADD</el-button> <el-button @click="reset">RESET</el-button>
methods: { add () { this.$emit('add') }, reset () { this.$emit('reset') } }
(4)Body組件:監控傳值,向上分發事件
<!-- 新增 --> <el-input-number v-model="currentVal" @change="handleChange"></el-input-number>
props: { num: { type: Number, default: 0 } }, data () { return { currentVal: 0 } }, // 外層數據改變時,currentVal值須要同步修改 watch: { num: { handler (val) { this.currentVal = val }, immediate: true } }, methods: { handleChange (val) { this.$emit('change', val) } }
這種簡單的數據交互使用prop和$emit足以應付,可是
(1)對於深層組件嵌套中的數據傳遞,使用這種通訊方式則須要一層一層向下prop,改變時須要一層一層向上$emit
(2)對於兄弟組件之間的數據傳遞,先要向上分發,再向下prop,過於繁瑣且不易監控調試
這裏有一個 新的需求:在最初組件組合的基礎上,side組件中有一個數據展現,要求經過body中深層嵌套的組件操做以改變side中的數據
修改:在body組件中添加<slot></slot>
,並新增一個組件掛載在該插槽上,用以模擬深層嵌套(固然了,實際的工做中的嵌套可能涉及到四層甚至更多)
上面方法的核心是全部子組件統一管理和操做父組件的數據,子組件負責展現和分發事件,實際操做值的始終在父組件,Vue提供了一個能訪問到根組件的方法,官網中如是描述:處理邊界狀況中訪問根實例部分
(1)在入口文件main.js
中添加:
new Vue({ data: { rootNum: 0 }, // ... })
(2)在父組件中添加:
<!-- 局部註冊不做詳述 --> <m-body> <m-body-item></m-body-item> </m-body>
(3)新添加的組件MBodyItem
<template> <div class="m-body-item"> <el-button @click="add">ADD</el-button> <el-button @click="reset">RESET</el-button> </div> </template> <script> // 可直接操做$root中聲明的變量 export default { methods: { add () { this.$root.rootNum += 1 }, reset () { this.$root.rootNum = 0 } } } </script>
(4)side組件:
<div class="m-side"> 我是側邊欄{{ $root.rootNum }} </div>
對於 demo 或很是小型的有少許組件的應用來講直接使用$root的方式很方便。不過這個模式擴展到中大型應用來講就否則了,數據量過大不易維護,也不易追蹤數據的變化
總線Bus的思路:將事件的註冊和觸發單獨放在一個Vue實例中,點擊按鈕時觸發指定的事件以驅動接下來的操做。Bus總線僅僅是用來驅動事件的,具體的數據操做仍是在原有的組件中
在$root的結構基礎上,做以下更改:
(1)原入口文件main.js
還原,去掉data屬性
(2)新定義一個總線文件bus.js
import Vue from 'vue' export default new Vue()
(3)side組件中註冊總線事件並顯示數據
import Bus from './bus' export default { data () { return { sideNum: 0 } }, created () { Bus.$on('change', (step) => { this.sideNum += step }) Bus.$on('reset', () => { this.sideNum = 0 }) } }
(4)bodyItem組件中分發總線事件
import Bus from './bus' export default { methods: { add () { Bus.$emit('change', 1) }, reset () { Bus.$emit('reset') } } }
總線的方式,將原有的數據傳遞轉換成了事件驅動的形式,這一點規避了組件層級的嵌套問題,可是開發人員沒法追蹤調試數據
因爲內容較多,將在下一篇博客中詳細介紹,敬請期待..