vue的學習曲線不是很陡(相比其它框架,如anglarjs),官方文檔比較全面,分爲基礎篇和高級篇。咱們剛開始學習的時候,確定像引用jquery那樣,先把vue的js引進來,而後學習基礎內容。若是僅僅停留在基礎內容,沒有學習vue組件的話,我以爲也就沒有什麼意思了。vue的核心思想——組件,是一個很好的東西,它提供了功能複用。css
一、單文件組件html
所謂單文件組件,顧名思義,一個vue格式的文件就是一個組件。比如python和js的模塊,文件即模塊。vue組件帶有本身的模板,能夠理解爲視圖,也帶有本身數據及邏輯。數據能夠從外部來,經過Prop接收。用圖形表示:vue
因而可知,單文件組件就是一個完整而獨立的體系。注意,style有個屬性scope表示僅做用於當前組件。wpf當中的控件,有本身的xaml(視圖),邏輯,能夠外部綁定數據源。我以爲vue組件相似wpf中的用戶控件。由於用戶控件組合了基礎的控件,好比Button、TextBlock等等。用戶控件可直接當成一個獨立的組件使用。它和vue組件同樣,都是從外部獲取特定的信息,而後構建本身內部的數據以及邏輯。其實組件體現的是面向對象中的「封裝」思想。python
二、動態組件&異步加載jquery
有時候讀了官方的文檔,仍是不明白,這時候就須要上網搜搜相關資料。比如《聖經》或者《道德經》中的經文是須要慢慢揣摩和體會的。固然vue的動態組件、以及組件的異步加載也是須要在實踐當中慢慢體會的。下來分享一個我在項目中使用的例子:web
在後臺管理頁面中,編輯、增長一條信息,這時候須要在彈出頁面中操做。所以,我就封裝了一個彈出模態框(帶有遮罩效果)的組件。而編輯頁面是另外的一個組件。因此,須要把編輯頁面的組件「送到」彈出框的組件中呈現。有點像「裝飾器模式」,不要原生態地呈現編輯組件,而是把它包裝一番,再呈現,以下圖:vuex
後臺管理中像這樣的編輯頁面很是多,因此彈出框組件的意義就在這,複用。我上面說了須要把「編輯軟件資源」的組件,送到彈出框組件顯示。如何送呢?其實也不難。我把這個組件做爲彈框組件的子組件。那麼這個彈框組件有不少個編輯組件。如今問題來了,如何控制它們顯示?當我點擊「編輯軟件資源」的時候,彈出對應的頁面,當我點擊編輯新聞的時候,它要彈出新聞的頁面,難道我要控制組件的顯示隱藏嗎?這個狀況有點像「Tab」,任什麼時候候,只能呈現一個TabItem,那麼其它的只能隱藏掉。好了,咱們也能夠這麼作。還有另一個問題,咱們如何導入這些組件,一次性import多個組件,貌似也沒有什麼問題。這會不會影響頁面加載的性能呢?我想確定會。app
我想到了秦腔中的「變臉」,對,這個頗有意思,一我的經過變臉能夠扮演多我的。和演員同樣,好比最近的一個電視劇中景甜扮演了「奉劍」和「千湄」兩個角色。vue裏面的動態組件就是如此,一個組件老是「扮演」各類組件。異步加載,當我須要用你的時候,再去import,這顯然是合理的。說了這麼多,咱們看看代碼:框架
1 <template> 2 <transition name="modal"> 3 <div class="modal-mask"> 4 <div class="modal-wrapper"> 5 <div class="modal-container" :style="{width:width,height:height}"> 6 7 <div class="modal-header"> 8 <slot name="header"> 9 {{title}} 10 </slot> 11 <button class="modal-default-button" @click="close"> 12 X 13 </button> 14 </div> 15 16 <div class="modal-body" :style="{height:bodyHeight,width:bodyWidth}"> 17 <slot name="body"> 18 <component :is="currentComponent" @close="close" :id="id"></component> 19 </slot> 20 </div> 21 22 </div> 23 </div> 24 </div> 25 </transition> 26 </template> 27 28 <style scoped> 29 .modal-mask { 30 position: fixed; 31 z-index: 9998; 32 top: 50%; 33 left: 50%; 34 width: 100%; 35 height: 100%; 36 background-color: rgba(0, 0, 0, .5); 37 display: table; 38 transform: translateX(-50%) translateY(-50%); 39 transition: opacity .3s ease; 40 } 41 42 .modal-wrapper { 43 display: table-cell; 44 vertical-align: middle; 45 } 46 47 .modal-container { 48 margin: 0px auto; 49 padding: 20px 30px; 50 background-color: #fff; 51 border-radius: 2px; 52 box-shadow: 0 2px 8px rgba(0, 0, 0, .33); 53 transition: all .3s ease; 54 font-family: Helvetica, Arial, sans-serif; 55 } 56 57 .modal-header h3 { 58 margin-top: 0; 59 color: #42b983; 60 } 61 62 .modal-body { 63 margin: 10px 0; 64 overflow-y: auto 65 } 66 67 .modal-default-button { 68 float: right; 69 background: none; 70 border: none; 71 cursor: pointer; 72 } 73 74 /* 75 * The following styles are auto-applied to elements with 76 * transition="modal" when their visibility is toggled 77 * by Vue.js. 78 * 79 * You can easily play with the modal transition by editing 80 * these styles. 81 */ 82 83 .modal-enter { 84 opacity: 0; 85 } 86 87 .modal-leave-active { 88 opacity: 0; 89 } 90 91 .modal-enter .modal-container, 92 .modal-leave-active .modal-container { 93 -webkit-transform: scale(1.1); 94 transform: scale(1.1); 95 } 96 </style> 97 98 <script> 99 export default { 100 props: { 101 title: { 102 type: String 103 }, 104 width: { 105 type: String, 106 required: false, 107 default: '30%' 108 }, 109 height: { 110 type: String, 111 required: false, 112 default: '65%' 113 }, 114 115 currentComponent: { 116 type: String, 117 required: true 118 }, 119 id: { 120 type: Number, 121 default: 0 122 } 123 }, 124 components: { 125 newsItem(resolve) { 126 require(['../Admin/newsItem'], resolve) 127 }, 128 softwareItem(resolve) { 129 require(['../Admin/softwareItem'], resolve) 130 } 131 }, 132 data() { 133 return { 134 bodyHeight: '98%', 135 bodyWidth: '100%' 136 } 137 }, 138 methods: { 139 close(type) { 140 if (type) 141 this.$emit('close', type); 142 else 143 this.$emit('close'); 144 } 145 } 146 } 147 </script>
Props中接收 currentComponent,要呈現哪一個組件,交給調用方,誰調用我,誰就必須告訴我,該顯示哪一個子組件。
三、組件通訊
組件間通訊問題,是一個廣泛問題。組件再獨立也得和其它組件協同完成任務吧。沒有一個組件能完成全部事情。常見的那就父子之間的通訊以及兄弟之間的通訊。有沒有父組件引起了一個事件,由子組件來處理呢?貌似沒有。若是有的話,就是父組件更改了Props中屬性的值。若是子組件非要在更改值
的時候,做出某些處理的話,那麼就用Watch了。
watch: { total(val, oldVal) { if (val != oldVal) { this.render(); } }, }
這個watch監視的是total(總頁數),是分頁組件監視Props中的total,一旦total改變,那麼分頁組件須要render,調用render方法從新渲染本身。dom
子組件觸發事件,父組件監聽,這是很是常見的。好比彈出框組件中的關閉事件,分頁組件中的 pageHandler 分頁事件,這些都要父組件來處理,子組件經過 $emit,這是vue全局的方法,哪一個組件均可以用。父組件必須監聽pageHandler事件:
<ym-pager v-if="total" :page-index="pageIndex" :page-size='pageSize' :total='total' :groups="5" @pageHandler="loadData"></ym-pager>
兄弟之間的通訊,如何解決呢?網上一搜,基本上都是給一個總線級別的組件,這個組件就是用來通信的,誰須要發佈事件,就往這裏發,誰須要處理,那麼就監聽相關事件。理論上能夠實現,可是我在實踐的過程當中,始終沒有成功,不知道爲何。還有一種思路,
經過vuex實現,事件發佈方,更改vuex中的某個狀態值,那麼監控方發現這個狀態有變化的時候,就去處理事件。vuex是一個集中式的狀態管理器。「天下有變,則命一上將將荊州之軍以向宛、洛。。。。。。」,《隆中對》反映了蜀漢對天下大勢要密切監視,一旦
發生了變化,就要採起行動了。兄弟之間的通訊,咱們項目還真沒有用到過,若是須要的同窗,可進一步查閱資料,這裏僅探討思路。
四、slot
這個特別有用,也有意思。插槽,它反映了一種IOC(控制反轉)的思想。原本子組件的呈現由本身作決定,但是某些狀況下,子組件的某一部分變數很大,須要抽象出來,就用了slot先佔着,等父組件調用的時候,再告訴該如何渲染。好比咱們有一個table組件,這個組件實現了分頁等功能。但是table的表頭和表的內容充滿着變數,如果由父組件經過Props傳遞,也能夠,就是特別麻煩,傳遞的東西太多了,並且子組件這邊也須要不少處理。大道至簡,用slot,簡潔。table組件不用那麼費勁。調用table的父組件也不用想着如何更好地傳遞數據了。
<table class="ym-table table-hover"> <slot name="thead"></slot> <slot name="tbody"></slot> </table>
table組件中定義了兩個命名slot,看看如何調用:
<YmTable :page-title="pageTitle" :total="totalCount" :page-size="pageSize" @pager="pager" @newItem="newItem"> <thead slot="thead"> <tr> <th>序號</th> <th>軟件名稱</th> <th>簡介</th> </tr> </thead> <tbody slot="tbody"> <tr v-for="(item,index) in items" :key="item.id"> <td v-text="getIndex(index)"></td> <td> {{item.name}} </td> <td> {{item.summary}} </td> </tr> </tbody> </YmTable>
五、vue生命週期
生命週期是個老生常談的問題。是個對象,那就總有個生命週期吧。好比.net中Page對象,頁面的生命週期,並且這個仍是主考官特別愛考的問題。Android的中Activity的生命週期。Page和Activity對象的功能有點像,提供用戶操做的界面,能夠簡單地理解爲UI。
網上最著名的就是這張圖:
這個圖,咱們大體理解一下,它核心就是如何把VM(虛擬的dom)轉換爲實際dom,並且在何時轉換。這裏有一點記住就好了,Created的時候,dom尚未被渲染出來,此時不宜操做dom相關的事情。Mounted的時候,作的事情就多了。好比,在mounted的時候,經過layui綁定form的提交事件。
mounted() { let that = this; var form = layui.form; //綁定form提交事件 layui.form.on('submit(*)', function (data) { that.summit(); return false; }); },
再例如,封裝了一個Select的組件,在updated的時候,執行select的render:
updated() { layui.form.render('select'); },
總之,vue生命週期中,都會留有鉤子函數,經過這些才能把咱們的業務邏輯注入到Vue對象中,並且獲得執行。咱們作一件事情,要看準時機,若是時機不對,事倍功半,甚至一敗塗地。諸葛亮出山的時機不對啊。
六、實例變量 && $nextTick
文檔中是這麼說的:將回調延遲到下次DOM更新循環以後執行。在修改數據以後當即使用這個方法,獲取更新後的DOM。很抽象啊,不理解。可是我須要它。我封裝了一個YmRichText組件,這個組件裏是調用了kindeditor,富文本框。
mounted() { let that = this; this.$nextTick(function () { that.kedit('textarea[name="content"]'); }); }, methods: { getConent() { return editor.html(); }, kedit(k) { let that = this; window.editor = KindEditor.create(k, { width: '98%', height: that.height + 'px', uploadJson: that.uploadFileUrl, allowFileManager: false }); } }
當在mounted的時候,無論怎麼樣建立的editor對象都爲空。因此使用了$nextTick。按理說,不該該啊,模板中有textarea,kindeditor的js和css也加載上了,並且也在mounted的時候調用的。可是反過來想,在$nextTick調用成功,說明在當前週期內,是不會調用kindeditor的方法的。咱們的分頁組件中,也使用了 $nextTick,這個倒好理解,由於在created時候,調用render,render方法中會操做dom,因此只能等下一個週期執行了。
created() { this.render(); }, watch: { total(val, oldVal) { if (val != oldVal) { this.render(); } }, pageIndex(val) { this.cindex = val; if (val == 1) { this.render(); } } }, methods: { render() { let self = this; this.$nextTick(function () { layui.laypage.render({ elem: self.pagerId, skin: self.cskin, count: self.total, //總數數 limit: self.pageSize, //每頁顯示條數 groups: self.cgroups, //連續顯示分頁數 curr: this.cindex, //當前頁 jump: function (obj, first) { if (!first) self.$emit("pageHandler", obj.curr); } }); }); } }
實例變量多了,好比引用父組件的$parent,引用子組件的$refs,$refs特別有用,好比要執行子組件裏的方法或者獲取子組件的數據。
<ym-company-select :oldCompanyId="oldCompanyId" ref="company"></ym-company-select>
this.data.companyId = this.$refs.company.companyId;
以上,就是我探討的vue組件開發的一些問題。