如今前端的快速发展,已经让组件这个模式变的格外重要。对于市面上的组件库,虽然能知足大部分的项目,可是一些小型细节方面和使用方面,或者UI库存在的一些bug,会让人很头疼。javascript
那咱们应该如何面对解决这些问题。俗话说本身动手丰衣足食。有些组件不用刻意去造。应该考虑如何去打造一个快速,兼容性好,功能齐全的组件库。html
先到github上和一些大公司开源的组件库官网上去看看你所需组件库的demo例子,Prop和event暴露出来了哪些接口。前端
货比三家。别人分别用了哪些模式设计,哪一种模式最简便,更合理。vue
通常成熟的UI库兼容性是通过大量测试和用户使用后没有问题后的结果。省去这一部分,进行样式的借鉴。java
不要太急于进行组件的大量造轮子。由于一我的的战斗是有限的。根据须要和场景项目进行一个个定位,聚沙成塔。node
Crib-zk也是我我的目前针对本身项目的需求,额外进行的组件。虽然不能用于开源市场的使用,可是能够用于你们的学习。使用和学习是两种模式。会使用不表明你懂,一旦有些需求不在开源项目组件的范围以内。此时又不能清楚内部的原理,就会措手不及。接下来进行一步一步分析。git
大纲:分析组件github
alert 插件/组件ajax
backtop 组件bash
sms-countDown组件(短信倒计时)
search 组件
infinitscroll 组件
actionSheet 组件
accordion 组件(手风琴)
github:github.com/ziksang/cri…
demo:http://39.108.49.237:3000/dist/
若是在alert这种弹出式组件里,首先要加一些背景layout的背景层动画化,能够简称为dialog动化,进行一个包裹。
对于alert组件/插件的区别使用性是在那里?
通常来讲,先会定义一个.vue文件的alert模板。
<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>复制代码
仔细看上面模板。
咱们发现惟一特别的是对content体中的定义了一个solt。这个slot就是组件模式和插件模式的区分。若是咱们想对slot里面定义的是一个额外的展现模板或其它组件插入的话,此时只能用组件模式。
若是只是咱们对content这个数据进填充的话,插件模式也是最方便的。
value 显示消息
title (标题)
content 内容最好支持html格式
buttonText底部的按钮文案
titleStyle 标题样式
buttonStyle button样式
onsubmit 点击时向外暴露事件
onshow 显示时向外暴露的显示事件
onhide 显示时向暴露事件
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组件的话,要理解几点属性。
scrollTop
offsetHeight
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;
});复制代码
须要监听两个事件:
二次加载load事件。一旦进行二次加载的时候,立刻进行isloading等于false 防止重复加载。
经过loadDone对是否监听完毕。若是加载完毕的话,一样的关闭isloading 对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组件要进行定义两个组件合并成一个组件的模式。
一个最外层的包裹组件。
第二个是每个item的组件。
每说 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是惟一的。这一步也是省了一些简便的操做。
在这里把一些突出的组件,来开阔咱们的思想,来进行一其它组件的封装 ,也能够基于这些组件对本身所须要的项目根据不一样的需求来封装。
最后,尤大说了一句话,我最喜欢的就是看别人代码。记住这句话,你的组件能写的又快又好。
这是我最近在Gitchat上交流的一篇文章。若是你们对Vue感兴趣的话,能够加入个人Vue讨论微信群。有什么问题能够在群里交流。