vue前端開發那些事——vue元件開發
vue的學習曲線不是很陡(相比其它框架,如anglarjs),官方文件比較全面,分為基礎篇和高階篇。我們剛開始學習的時候,肯定像引用jquery那樣,先把vue的js引進來,然後學習基礎內容。如果僅僅停留在基礎內容,沒有學習vue元件的話,我覺得也就沒有什麼意思了。vue的核心思想——元件,是一個很好的東西,它提供了功能複用。
1、單檔案元件
所謂單檔案元件,顧名思義,一個vue格式的檔案就是一個元件。好比python和js的模組,檔案即模組。vue元件帶有自己的模板,可以理解為檢視,也帶有自己資料及邏輯。資料可以從外部來,通過Prop接收。用圖形表示:
由此可見,單檔案元件就是一個完整而獨立的體系。注意,style有個屬性scope表示僅作用於當前元件。wpf當中的控制元件,有自己的xaml(檢視),邏輯,可以外部繫結資料來源。我覺得vue元件類似wpf中的使用者控制元件。因為使用者控制元件組合了基礎的控制元件,比如Button、TextBlock等等。使用者控制元件可直接當成一個獨立的元件使用。它和vue元件一樣,都是從外部獲取特定的資訊,然後構建自己內部的資料以及邏輯。其實元件體現的是面向物件中的“ 封裝 ”思想。
2、動態元件&非同步載入
有時候讀了官方的文件,還是不明白,這時候就需要上網搜搜相關資料。好比《聖經》或者《道德經》中的經文是需要慢慢揣摩和體會的。當然vue的動態元件、以及元件的非同步載入也是需要在實踐當中慢慢體會的。下來分享一個我在專案中使用的例子:
在後臺管理頁面中,編輯、增加一條資訊,這時候需要在彈出頁面中操作。因此,我就封裝了一個 彈出模態框 (帶有遮罩效果)的元件。而 編輯頁面 是另外的一個元件。所以, 需要把編輯頁面的元件“送到”彈出框的元件 中呈現。有點像 “裝飾器模式” ,不要原生態地呈現編輯元件,而是把它包裝一番,再呈現,如下圖:
後臺管理中像這樣的編輯頁面非常多,所以彈出框元件的意義就在這,複用。我上面說了需要把“編輯軟體資源”的元件,送到彈出框元件顯示。如何送呢?其實也不難。我把這個元件作為彈框元件的子元件。那麼這個彈框元件有很多個編輯元件。現在問題來了,如何控制它們顯示?當我點選“編輯軟體資源”的時候,彈出對應的頁面,當我點選編輯新聞的時候,它要彈出新聞的頁面,難道我要控制組件的顯示隱藏嗎?這個情況有點像“Tab”,任何時候,只能呈現一個TabItem,那麼其它的只能隱藏掉。好了,我們也可以這麼做。還有另外一個問題,我們如何匯入這些元件,一次性import多個元件,貌似也沒有什麼問題。這會不會影響頁面載入的效能呢?我想肯定會。
我想到了秦腔中的“變臉”,對,這個很有意思,一個人通過變臉可以扮演多個人。和演員一樣,比如最近的一個電視劇中 景甜 扮演了“奉劍”和“千湄”兩個角色。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"> 12X 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 { 30position: fixed; 31z-index: 9998; 32top: 50%; 33left: 50%; 34width: 100%; 35height: 100%; 36background-color: rgba(0, 0, 0, .5); 37display: table; 38transform: translateX(-50%) translateY(-50%); 39transition: opacity .3s ease; 40 } 41 42 .modal-wrapper { 43display: table-cell; 44vertical-align: middle; 45 } 46 47 .modal-container { 48margin: 0px auto; 49padding: 20px 30px; 50background-color: #fff; 51border-radius: 2px; 52box-shadow: 0 2px 8px rgba(0, 0, 0, .33); 53transition: all .3s ease; 54font-family: Helvetica, Arial, sans-serif; 55 } 56 57 .modal-header h3 { 58margin-top: 0; 59color: #42b983; 60 } 61 62 .modal-body { 63margin: 10px 0; 64overflow-y: auto 65 } 66 67 .modal-default-button { 68float: right; 69background: none; 70border: none; 71cursor: 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 { 84opacity: 0; 85 } 86 87 .modal-leave-active { 88opacity: 0; 89 } 90 91 .modal-enter .modal-container, 92 .modal-leave-active .modal-container { 93-webkit-transform: scale(1.1); 94transform: scale(1.1); 95 } 96 </style> 97 98 <script> 99 export default { 100props: { 101title: { 102type: String 103}, 104width: { 105type: String, 106required: false, 107default: '30%' 108}, 109height: { 110type: String, 111required: false, 112default: '65%' 113}, 114 115currentComponent: { 116type: String, 117required: true 118}, 119id: { 120type: Number, 121default: 0 122} 123}, 124components: { 125newsItem(resolve) { 126require(['../Admin/newsItem'], resolve) 127}, 128softwareItem(resolve) { 129require(['../Admin/softwareItem'], resolve) 130} 131}, 132data() { 133return { 134bodyHeight: '98%', 135bodyWidth: '100%' 136} 137}, 138methods: { 139close(type) { 140if (type) 141this.$emit('close', type); 142else 143this.$emit('close'); 144} 145} 146 } 147 </script>
Props中接收 currentComponent,要呈現哪個元件,交給呼叫方,誰呼叫我,誰就必須告訴我,該顯示哪個子元件。
3、元件通訊
元件間通訊問題,是一個普遍問題。元件再獨立也得和其它元件協同完成任務吧。沒有一個元件能完成所有事情。常見的那就父子之間的通訊以及兄弟之間的通訊。有沒有父元件引發了一個事件,由子元件來處理呢?貌似沒有。如果有的話,就是父元件更改了Props中屬性的值。如果子元件非要在更改值
的時候,作出某些處理的話,那麼就用Watch了。
props: ["pageIndex", "pageSize", "total", "groups", "skin"], watch: { total(val, oldVal) { if (val != oldVal) { this.render(); } }, }
這個watch監視的是total(總頁數),是分頁元件監視Props中的total,一旦total改變,那麼分頁元件需要render,呼叫render方法重新渲染自己。
子元件觸發事件,父元件監聽,這是非常常見的。比如彈出框元件中的 關閉事件 ,分頁元件中的 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是一個集中式的狀態管理器。“天下有變,則命一上將將荊州之軍 以向宛、洛 。。。。。。”,《隆中 對 》反映了蜀漢對天下大勢要密切監視,一旦
發生了變化,就要採取行動了。兄弟之間的通訊,我們專案還真沒有用到過,如果需要的同學,可進一步查閱資料,這裡僅探討思路。
4、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>
5、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物件中,而且得到執行。我們做一件事情,要看準時機,如果時機不對,事倍功半,甚至一敗塗地。諸葛亮出山的時機不對啊。
6、例項變數 && $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元件開發的一些問題。