來自 GitChat 做者:周志祥
更多IT技術分享,盡在微信公衆號:GitChat技術雜談javascript
如今前端的快速發展,已經讓組件這個模式變的格外重要。對於市面上的組件庫,雖然能知足大部分的項目,可是一些小型細節方面和使用方面,或者UI庫存在的一些bug,會讓人很頭疼。html
那咱們應該如何面對解決這些問題。俗話說本身動手豐衣足食。有些組件不用刻意去造。應該考慮如何去打造一個快速,兼容性好,功能齊全的組件庫。前端
Crib-zk也是我我的目前針對本身項目的需求,額外進行的組件。雖然不能用於開源市場的使用,可是能夠用於你們的學習。使用和學習是兩種模式。會使用不表明你懂,一旦有些需求不在開源項目組件的範圍以內。此時又不能清楚內部的原理,就會措手不及。接下來進行一步一步分析。vue
大綱:分析組件java
注意 : 看這篇文章最好結合我github上發佈的組件,進行比對式觀看。node
若是在alert這種彈出式組件裏,首先要加一些背景layout的背景層動畫化,能夠簡稱爲dialog動化,進行一個包裹。git
對於alert組件/插件的區別使用性是在那裏?github
通常來講,先會定義一個.vue文件的alert模板。ajax
<template> <div class="vux-alert"> <x-dialog :value='alertShow' :isClose='false'> <div class="crib-confirm_hd" :style='[titleStyle]'> <strong class="crib-confirm_title">{{title}}</strong> </div> <div class="crib-confirm_bd"> <slot> <div v-html="content"></div> </slot> </div> <div class="crib-confirm_ft"> <a href="javascript:;" class="crib-confirm_btn crib-confirm_btn_primary" @click="_onSubmit" :style='[buttonStyle]'>{{buttonText}}</a> </div> </x-dialog> </div> </template>
仔細看上面模板。微信
data() { return { alertShow: this.value } }, watch: { value(val) { this.alertShow = val } }, methods: { _onSubmit() { this.alertShow = false this.$emit('update:value', false) this.$emit('on-submit') } }
對於value這個值來講,能夠用.sync來進行簡便的操做。不須要經過$emit來進行通知。在聲明組件的時候用$on去進行監聽事件,省去了開發者這一步的事。
首先要把本來的alert的.vue的模板給引處進來,而後用Vue.extend繼承一下。
$vm = new Alert({ el: document.createElement('div') })
咱們本身要手動進行一個建立掛載點。
//此方法是用來把confirm上的prop屬性合併到調用時的參數上 const mergeOptions = function($vm,options) { //聲明一個默認的對象,就是comfirm上props屬性的default的值 const defaults = {} //循環confirm屬性上的props值 for (let i in $vm.$options.props){ //不把value的值算上去,顯示改變經過watch或者改變data代理的屬性上去監聽 if(i !== 'value'){ defaults[i] = $vm.$options.props[i].default } } //把confrim組件本來的值和插件傳入的options合併 const _options = Object.assign({},defaults,options) //把confirm組件生成的實列對象再次替換成合並的屬性 for(let i in _options) { $vm[i] = _options[i] } }
同時要把value顯示操做的默認定義的屬性除外,進行本身定義後覆蓋默認屬性,進行顯示。
$watcher = $vm.$watch('alertShow', (val) => { if (!val && options && options.onHide) { options.onHide() } })
同時對alertshow進行監聽,當點擊submit的時候會本身動觸發事件,而後會改變alertshow的值,而後進行你所想要的操做。
對於backtop組件的話,要理解幾點屬性。
let offsetTop = this.scrollview == window ? document.body.scrollTop : this.scrollview.scrollTop let offsetHeight = this.scrollview == window ? document.body.offsetHeight : this.scrollview.offsetHeight
scrollTop 是距離頂部的高度。
offsetHeight 元素的高度包括邊框。
那如何去判斷何時顯示返回頂部按鈕呢?
this.show = offsetTop >= offsetHeight / 2;
只要經過滾動的高度大於滾動元素的高度/2來進行一個適配是最好的。
對於如何進行那方面優化。
能夠進行函數節流。節流是個什麼?由於進行滾動監聽的時候,scroll事件觸發的太頻繁了。這會影響到整個性能的問題。若是對於上下滾動也要頻繁監聽。用節點,不適用於防抖操做。
throttle(func, wait) { var context, args; var previous = 0; return function () { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } },
經過時間戳來進行對比,來進行函數節流。可是有一點須要注意,在節流的同時,不要節流的時節太長。由於mobile上面節流滾動的話,有一個自行滑動的時長。
const getScrollview = function (el) { //拿到當前節點 let currentNode = el; //若是有節點,而且節點不等於html ,body 而且節點類型是元素節點 while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) { //拿到節點的overflowy的屬性 let overflowY = document.defaultView.getComputedStyle(currentNode).overflowY; //若是此時屬性是scroll或者atuo 就返回此節點 if (overflowY === 'scroll' || overflowY === 'auto') { return currentNode; } //不然就繼續向父節點上找 currentNode = currentNode.parentNode; } //一但while語句爲false的時候就直接返回window對像 return window; }; export {getScrollview}
在外層要進行一個包裹,通overflow屬性向來進行推測, 是全局滾動仍是window 下的滾動,經過while來進行判斷遞歸,來查找所對應的元素 。
對於短信倒計時最重要的一點就是從父組件向sms組件通知倒計時開始的一個prop參數,用start替代。
watch : { start (value) { if(value === true){ this.countDown() } } }
同時對start在內部進行監聽。一旦從外部傳入開始的時候,則內部進行倒計時。
countDown () { this.myTime = 60; let time = setInterval(()=>{ this.myTime --; if(this.myTime === 0){ this.$emit('update:start', false); this.myTime = this.initText; this.flag = true; clearInterval(time) } },100) }
在這裏一樣要進行一個倒計時中止以後的向外通知。仍是用.sync的雙向綁定方法,用於簡便操做。
在對於第一次倒計時和第二次倒計時的時候,也要對文案這方面進行一個設定。
firstCkText : { type : String, default : '' }, secondCKText : { type : String, default : '從新獲取' },
第一次點擊和第二次點擊的按鈕,也是對主要的文案的一種設計,因此對文案的變化也是要很關注的。
對於search組件一般能想到哪些對應的功能和想法呢?好比首次進來的時候,要進行自動獲取Input的焦點。同時要向外面暴露是否要獲取Input獲點事的Prop :autoFocus。同時也要注意,必定要在Mounted的時候才能拿 到dom元素。
mounted() { this.autoFocus && this.$refs.input.focus() }
通常想知道input裏面的value值是否改動的時候,一般都會用keydown或者是keyup事件。可是這裏不須要,能夠時時把value的值給暴露出去,讓外層父組件能夠去進行watch監聽來進行進所須要的事件操做。
watch: { inputValue (val) { if(val == ''){ this.value = "" } }, value: { handler(val, oldvalue) { //當值改變的時候,觸發事件 this.$emit('update:inputValue', val) this.$emit('change-val') }, immediate: true } },
同時這裏用到了.sync ,在頁面一加載的時候,立馬執行了。immediate使得value這個值立馬值行了監聽。
無限滾動最關鍵的三個地方。第一滾動動底部觸發事件;第二若是有二次加載則顯示 loading;第三如何沒有二次加載則結束文案。
import { getScrollview } from '../../libs/getScrollview.js';
這個不用說,繼續尋找須要滾動範圍的元素 。
data() { return { isLoading: false, //是否正在加載 isDone: false, //是否加載完畢 } },
data裏面進行以前說的兩種模式的狀態進行定義。往下看,這一處定義以後對後面有什麼好處。
this.$on('Load', () => { this.isLoading = false; }); this.$on('loadDone', () => { this.isLoading = false; this.isDone = true; });
須要監聽兩個事件:
isloading和isDone分別對應的那個html 的template部分。
<div class="list-donetip" v-show='!isLoading&& isDone'> <slot name='doneTip'>沒有更多數據了</slot> </div> <div class="list-loading" v-show='isLoading'> <slot name='loadingTip'>加載中...</slot> </div>
當isloading 爲true的時候顯示「加載中... 」當isloading 爲false的時候,isDone爲true的時候才顯示 「沒有更多數據」,這也是一個標準的無限滾動。
何時對isloading和isDone設置爲true?
scrollHandler() { if (this.isLoading || this.isDone) return; let baseHeight = this.scrollview == window ? document.body.offsetHeight : this.scrollview.offsetHeight let moreHeight = this.scrollview == window ? document.body.scrollHeight : this.scrollview.scrollHeight; let scrollTop = this.scrollview == window ? document.body.scrollTop : this.scrollview.scrollTop if (baseHeight + scrollTop + this.distance > moreHeight) { this.isLoading = true; this.onInfinite() } if (!this.scrollview) { console.warn('Can\'t find the scrollview!'); return; } },
當滾動到底部的時候,對isloading進行爲true的設置。外部組件能夠調用onInfinite,進行ajax請求等操做。
在外部如何調用呢?
this.$refs.infinite.$emit('loadDone')
對組件進行 ref的設置,而後進行觸發loadDone或者load。
比對餓麼的組件,它使用的是指令的模式,內部實現仍是太複雜。
這裏就用到了函數防抖,同上面不用函數節流,用防抖。防抖跟節流的有什麼區別?防抖比較更節省性能。若是咱們在設置的時間內一直滑動,則不會進行加載,只有滑動到指定的地方,則能夠進行檢測,經過定時器來實現。
debounce (func, wait) { var timeout; return function () { var context = this; var args = arguments; clearTimeout(timeout) timeout = setTimeout(function () { func.apply(context, args) }, wait); } },
actionSheet 這裏亮點就是巧用了。call方法來改變了this的指向。這個有什麼好處?往下面看。
prpps : model 我經過Model這個數據進行遞,把文案的改變,點擊後所執行的方法一併封裝到Model數據裏來進行操做。
若是在父組件引入這個組件的時候,看下面代碼。
model: [ { name: this.name, method(name, index) { location.href = 'tel:110' } } ],
若是進行this.指向的話,指向的是父組件。這裏就不能直接在data裏面聲明瞭。若是是異步的話,只有在ajax請求的異步裏進行聲明,把值傳入,是如何作的呢?
ff (item,index,method) { this.$emit('update:show', false) method.call(this.$parent,item,index) }
在這裏經過.call來把this.的指向到父組件,就能成功的方便的調用了。
對accordion組件要進行定義兩個組件合併成一個組件的模式。
每說 accordion-item裏面的組件,經過
this.height = (this.show ? this.$refs.content.offsetHeight: 0) + 'px';
若是須要顯示的話,讓每個item的元素來計算高度,展示出來。
經過_uid來進行 每一個item的識別。
methods : { open(uid) { this.$children.forEach (item => { console.log(item._uid) if(item._uid == uid){ item.show = !item.show }else{ if(!this.repeat){ item.show = false item.height = 0; } } }) } }
可以收起的是那個item組件,則向收起的那個item組件進行一個傳遞。本質上經過index找到子組件對應的項也能夠實現。由於_uid是惟一的。這一步也是省了一些簡便的操做。
在這裏把一些突出的組件,來開闊咱們的思想,來進行一其它組件的封裝 ,也能夠基於這些組件對本身所須要的項目根據不一樣的需求來封裝。
最後,尤大說了一句話,我最喜歡的就是看別人代碼。記住這句話,你的組件能寫的又快又好。