最近在寫fj-service-system的時候,遇到了一些問題。那就是我有些組件,好比Dialog、Message這樣的組件,是引入三方組件庫,好比element-ui這樣的,仍是本身實現一個?雖然它們有按需引入的功能,可是總體風格和個人整個系統不搭。因而就能夠考慮本身手動實現這些簡單的組件了。javascript
一般咱們看Vue的一些文章的時候,咱們能看到的一般是講Vue單文件組件化開發頁面的。單一組件開發的文章相對就較少了。我在作fj-service-system項目的時候,發現其實單一組件開發也是頗有意思的。能夠寫寫記錄下來。由於寫的不是什麼ui框架,因此也只是一個記錄,沒有github倉庫,權且看代碼吧。html
- v-model或者.sync顯式控制組件顯示隱藏
- 經過js代碼調用
- 經過Vue指令調用
在寫組件的時候不少寫法、靈感來自於element-ui,感謝。vue
Dialog
我習慣把這個東西叫作對話框,實際上還有叫作modal(彈窗)組件的叫法。其實就是在頁面裏,彈出一個小窗口,這個小窗口裏的內容能夠定製。一般能夠用來作登陸功能的對話框。java
這種組件就很適合經過v-model或者.sync的方式來顯式的控制出現和消失。它能夠直接寫在頁面裏,而後經過data去控制——這也是最符合Vue的設計思路的組件。git
爲此咱們能夠寫一個組件就叫作Dialog.vuegithub
<template> <div class="dialog"> <div class="dialog__wrapper" v-if="visble" @clcik="closeModal"> <div class="dialog"> <div class="dialog__header"> <div class="dialog__title">{{ title }}</div> </div> <div class="dialog__body"> <slot></slot> </div> <div class="dialog__footer"> <slot name="footer"></slot> </div> </div> </div> <div class="modal" v-show="visible"></div> </div> </template> <script> export default { name: 'dialog', props: { title: String, visible: { type: Boolean, default: false } }, methods: { close() { this.$emit('update:visible', false) // 傳遞關閉事件 }, closeModal(e) { if (this.visible) { document.querySelector('.dialog').contains(e.target) ? '' : this.close(); // 判斷點擊的落點在不在dialog對話框內,若是在對話框外就調用this.close()方法關閉對話框 } } } } </script>
CSS什麼的就不寫了,跟組件自己關係比較小。不過值得注意的是,上面的dialog__wrapper這個class也是全屏的,透明的,主要用於獲取點擊事件並鎖定點擊的位置,經過DOM的Node.contains()方法來判斷點擊的位置是否是dialog自己,若是是點擊到了dialog外面,好比半透明的modal層那麼就派發關閉事件,把dialog給關閉掉。django
當咱們在外部要調用的時候,就能夠以下調用:element-ui
<template> <div class="xxx"> <dialog :visible.sync="visible"></dialog> <button @click="openDialog"></button> </div> </template> <script> import Dialog from 'Dialog' export default { components: { Dialog }, data() { return { visible: false } }, methods: { openDialog() { this.visible = true // 經過data顯式控制dialog } } } </script>
爲了Dialog開啓和關閉好看點,你可試着加上<transition></transition>組件配合上過渡效果,簡單的一點過渡動效也將會很好看。api
Notice
這個組件相似於element-ui的message(消息提示)。它吸引個人最大的地方在於,它不是經過顯式的在頁面裏寫好組件的html結構經過v-model去調用的,而是經過在js裏經過形如this.$message()這樣的方法調用的。這種方法雖然跟Vue的數據驅動的思想有所違背。不過不得不說在某些狀況下真的特別方便。markdown
對於Notice這種組件,一次只要提示幾個文字,給用戶簡單的消息提示就好了。提示的信息多是多變的,甚至能夠出現疊加的提示。若是經過第一種方式去調用,事先就得寫好html結構,這無疑是麻煩的作法,並且沒法預知有多少消息提示框。而經過js的方法調用的話,只須要考慮不一樣狀況調用的文字、類型不一樣就能夠了。
而以前的作法都是寫一個Vue文件,而後經過components屬性引入頁面,顯式寫入標籤調用的。那麼如何將組件經過js的方法去調用呢?
這裏的關鍵是Vue的extend方法。
文檔裏並無詳細給出extend能這麼用,只是做爲須要手動mount的一個Vue的組件構造器說明了一下而已。
經過查看element-ui的源碼,纔算是理解了如何實現上述的功能。
首先依然是建立一個Notice.vue的文件
<template> <div class="notice"> <div class="content"> {{ content }} </div> </div> </template> <script> export default { name: 'notice', data () { return { visible: false, content: '', duration: 3000 } }, methods: { setTimer() { setTimeout(() => { this.close() // 3000ms以後調用關閉方法 }, this.duration) }, close() { this.visible = false setTimeout(() => { this.$destroy(true) this.$el.parentNode.removeChild(this.$el) // 從DOM裏將這個組件移除 }, 500) } }, mounted() { this.setTimer() // 掛載的時候就開始計時,3000ms後消失 } } </script>
上面寫的東西跟普通的一個單文件Vue組件沒有什麼太大的區別。不過區別就在於,沒有props了,那麼是如何經過外部來控制這個組件的顯隱呢?
因此還須要一個js文件來接管這個組件,並調用extend方法。同目錄下能夠建立一個index.js的文件。
import Vue from 'vue' const NoticeConstructor = Vue.extend(require('./Notice.vue')) // 直接將Vue組件做爲Vue.extend的參數 let nId = 1 const Notice = (content) => { let id = 'notice-' + nId++ const NoticeInstance = new NoticeConstructor({ data: { content: content } }) // 實例化一個帶有content內容的Notice NoticeInstance.id = id NoticeInstance.vm = NoticeInstance.$mount() // 掛載可是並未插入dom,是一個完整的Vue實例 NoticeInstance.vm.visible = true NoticeInstance.dom = NoticeInstance.vm.$el document.body.appendChild(NoticeInstance.dom) // 將dom插入body NoticeInstance.dom.style.zIndex = nId + 1001 // 後插入的Notice組件z-index加一,保證能蓋在以前的上面 return NoticeInstance.vm } export default { install: Vue => { Vue.prototype.$notice = Notice // 將Notice組件暴露出去,並掛載在Vue的prototype上 } }
這個文件裏咱們能看到經過NoticeConstructor咱們可以經過js的方式去控制一個組件的各類屬性。最後咱們把它註冊進Vue的prototype上,這樣咱們就能夠在頁面內部使用形如this.$notice()方法了,能夠方便調用這個組件來寫作出簡單的通知提示效果了。
固然別忘了這個至關於一個Vue的插件,因此須要去主js裏調用一下Vue.use()方法:
// main.js // ... import Notice from 'notice/index.js' Vue.use(Notice) // ...
Loading
在看element-ui的時候,我也發現了一個頗有意思的組件,是Loading,用於給一些須要加載數據等待的組件套上一層加載中的樣式的。這個loading的調用方式,最方便的就是經過v-loading這個指令,經過賦值的true/false來控制Loading層的顯隱。這樣的調用方法固然也是很方便的。並且能夠選擇整個頁面Loading或者某個組件Loading。這樣的開發體驗天然是很好的。
其實跟Notice的思路差很少,不過由於涉及到directive,因此在邏輯上會相對複雜一點。
平時若是不涉及Vue的directive的開發,多是不會接觸到modifiers、binding等概念。參考文檔
簡單說下,形如:v-loading.fullscreen="true"這句話,v-loading就是directive,fullscreen就是它的modifier,true就是binding的value值。因此,就是經過這樣簡單的一句話實現全屏的loading效果,而且當沒有fullscreen修飾符的時候就是對擁有該指令的元素進行loading效果。組件經過binding的value值來控制loading的開啓和關閉。(相似於v-model的效果)
其實loading也是一個實際的DOM節點,只不過要把它作成一個方便的指令還不是特別容易。
首先咱們須要寫一下loading的Vue組件。新建一個Loading.vue文件
<template> <transition name="loading" @after-leave="handleAfterLeave"> <div v-show="visible" class="loading-mask" :class={'fullscreen': fullscreen}> <div class="loading"> ... </div> <div class="loading-text" v-if="text"> {{ text }} </div> </div> </transition> </template> <script> export default { name: 'loading', data () { return { visible: true, fullscreen: true, text: null } }, methods: { handleAfterLeave() { this.$emit('after-leave'); } } } </script> <style> .loading-mask{ position: absolute; // 非全屏模式下,position是absolute z-index: 10000; background-color: rgba(255,235,215, .8); margin: 0; top: 0; right: 0; bottom: 0; left: 0; transition: opacity .3s; } .loading-mask.fullscreen{ position: fixed; // 全屏模式下,position是fixed } // ... </style>
Loading關鍵是實現兩個效果:
1.全屏loading,此時能夠經過插入body下,而後將Loading的position改成fixed,插入body實現。
2.對所在的元素進行loading,此時須要對當前這個元素的的position修改:若是不是absolute的話,就將其修改成relatvie,並插入當前元素下。此時Loading的position就會相對於當前元素進行絕對定位了。
因此在當前目錄下建立一個index.js的文件,用來聲明咱們的directive的邏輯。
import Vue from 'vue' const LoadingConstructor = Vue.extend(require('./Loading.vue')) export default { install: Vue => { Vue.directive('loading', { // 指令的關鍵 bind: (el, binding) => { const loading = new LoadingConstructor({ // 實例化一個loading el: document.createElement('div'), data: { text: el.getAttribute('loading-text'), // 經過loading-text屬性獲取loading的文字 fullscreen: !!binding.modifiers.fullscreen } }) el.instance = loading; // el.instance是個Vue實例 el.loading = loading.$el; // el.loading的DOM元素是loading.$el el.loadingStyle = {}; toggleLoading(el, binding); }, update: (el, binding) => { el.instance.setText(el.getAttribute('loading-text')) if(binding.oldValue !== binding.value) { toggleLoading(el, binding) } }, unbind: (el, binding) => { // 解綁 if(el.domInserted) { if(binding.modifiers.fullscreen) { document.body.removeChild(el.loading); }else { el.loading && el.loading.parentNode && el.loading.parentNode.removeChild(el.loading); } } } }) const toggleLoading = (el, binding) => { // 用於控制Loading的出現與消失 if(binding.value) { Vue.nextTick(() => { if (binding.modifiers.fullscreen) { // 若是是全屏 el.originalPosition = document.body.style.position; el.originalOverflow = document.body.style.overflow; insertDom(document.body, el, binding); // 插入dom } else { el.originalPosition = el.style.position; insertDom(el, el, binding); // 若是非全屏,插入元素自身 } }) } else { if (el.domVisible) { el.instance.$on('after-leave', () => { el.domVisible = false; if (binding.modifiers.fullscreen && el.originalOverflow !== 'hidden') { document.body.style.overflow = el.originalOverflow; } if (binding.modifiers.fullscreen) { document.body.style.position = el.originalPosition; } else { el.style.position = el.originalPosition; } }); el.instance.visible = false; } } } const insertDom = (parent, el, binding) => { // 插入dom的邏輯 if(!el.domVisible) { Object.keys(el.loadingStyle).forEach(property => { el.loading.style[property] = el.loadingStyle[property]; }); if(el.originalPosition !== 'absolute') { parent.style.position = 'relative' } if (binding.modifiers.fullscreen) { parent.style.overflow = 'hidden' } el.domVisible = true; parent.appendChild(el.loading) // 插入的是el.loading而不是el自己 Vue.nextTick(() => { el.instance.visible = true; }); el.domInserted = true; } } } }
一樣,寫完整個邏輯,咱們須要將其註冊到項目裏的Vue下:
// main.js // ... import Loading from 'loading/index.js' Vue.use(Loading) // ...
至此咱們已經可使用形如
<div v-loading.fullscreen="loading" loading-text="正在加載中">
這樣的方式來實現調用一個loading組件了。
總結
在用Vue寫咱們的項目的時候,不論是寫頁面仍是寫形如這樣的功能型組件,其實都是一件頗有意思的事情。本文介紹的三種調用組件的方式,也是根據實際狀況出發而實際操做、實現的。不一樣的組件經過不一樣的方式去調用,方便了開發人員,也能更好地對代碼進行維護。固然也許還有其餘的方式,我並無瞭解,也歡迎你們在評論裏指出!
最後再次感謝element-ui的源碼給予的極大啓發。
https://www.cnblogs.com/wwhhq/p/8283769.html
https://www.cnblogs.com/crazycode2/p/7927473.html