概述
前提背景:大前端的时代造就了前端的业务逻辑越来越重,原有的MVP设计开发模式(jquery时代)造成了前端界面和数据操作都集中在MVP模式中的P层架构即控制器上,P层承担了大量的逻辑代码和操作视图DOM代码,两者交互在一起耦合性很强并且很重,而V(视图层)、M(数据层)的比重很轻,这对于业务功能越来越复杂的前端应用来说,大型的应用程序使用MVP模式已经不再是很好的选择。MVVM模式的诞生给我们构建复杂应用程序提供了极佳的管理模式和开发模式,VM层替代了原有的P层,它自动的处理dom和数据之间的关系,从而将我们把注意力集中在M层。随着MVVM越来越流行,遵循MVVM模式的Angular、React、Vue三大框架的应运而生,为我们开发大前端应用提供了极大的便利性。下面通过一小段代码对比MVP模式和MVVM之间的区别:
程序目标:实现简单的todolist效果:
MVP设计模式实现代码:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<title>jquery todolist</title>
<script src="../js/jquery-3.2.1.js"></script>
</head>
<body>
<div>
<input id="input" type="text">
<button id="btn">提交</button>
<ul id="list"></ul>
</div>
</body>
<script>
/*
* jquery开发遵循MVP模式,M模型 V视图 P控制器*/
// js构造函数
function Page() {
}
// 给Page对象实例增加一个方法init,调用bindEvents()方法,这个方法给button绑定一个点击事件
// $.proxy()方法将this.handleBtnClick()方法绑定到this即page实例
$.extend(Page.prototype, {
init: function () {
this.bindEvents();
},
bindEvents: function () {
var btn = $("#btn");
btn.on('click', $.proxy(this.handleBtnClick, this));
},
handleBtnClick: function () {
var inputElem = $("#input");
var inputVal = inputElem.val();
var ulElem = $("#list");
if(!inputVal){
window.alert('你输入的内容为空,不允许添加!');
return;
}
ulElem.append('<li>' + inputVal + '</li>');
inputElem.val('');
}
});
// 实例化对象调用方法
var page = new Page();
page.init();
</script>
</html>
MVP模式写的代码可以看出大量的逻辑代码和操作dom的代码交织在P控制层里,使用jquery开发遵循MVP模式的项目里,60%-70%的代码都和dom操作有关,造成了前端代码很重并且容易出错。
MVVM模式实现同样效果,使用vue框架实现:
<!DOCTYPE html><html>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<title>vue 实现todolist</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<input v-model="inputVal" type="text">
<button v-on:click="handleBtnClick">提交</button>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
</ul>
</div>
</body>
<script>
var app = new Vue({
el: "#app",
data: {
inputVal: '',
list: []
},
methods: {
handleBtnClick: function () {
this.list.push(this.inputVal);
this.inputVal = '';
}
}
});
</script></html>
MVVM模式包含视图层(V)、数据层(M)、VM(层),不在包含控制器P层,而VM层是VUE框架内部实现的,我们不需要关心,通过上述实例可以看出我们进行的都是对数据的处理,数据发生改变,dom结构随之发生变化,VM层就是实现这一过程的机制。
再来引入一个思想:前端组件化思想,前端页面的每个可视区域都可以看做是一个个组件构成的,组件化的好处是当我们的前端界面功能复杂时,我们可以拆分成一个个组件,然后在每个组件里写相应的dom结构,js,css等,通过webpack等工程化工具使我们的组件组合成一个大界面,方便我们以后的维护和代码的复用,而我们所要做的上拉刷新下拉加载组件就是基于这一思想和vue的模式来实现的。
先来认识一下better-scrol,该项目的github地址是,点击打开链接
插件作者是黄轶,引用作者的一篇文章当better-scroll遇见vue,里面详细解释了better-scroll的滚动原理和上拉加载,下拉刷新所需的API,
废话不多说,直接贴源码,亲测可以完美正常使用!
scroll.vue
<div class="scroll-wrapper" ref="wrapper">
<div class="scroll-main">
<!-- 顶部提示信息 -->
<div class="top-tip">
<div class="refresh-top"><span v-show="IsRefresh" class="icon-jiazai"></span><span
class="refresh-hook">下拉刷新</span></div>
</div>
<slot></slot>
<div class="bottom-tip">
<span v-show="IsLoading" class="icon-jiazai"></span><span class="loading-hook">查看更多</span>
</div>
</div>
<div class="refresh-success alert-hook">刷新成功</div>
</div>
</template>
<script type="text/ecmascript-6">
import BScroll from 'better-scroll';
export default {
props: {
/**
* 1 滚动的时候会派发scroll事件,会截流。
* 2 滚动的时候实时派发scroll事件,不会截流。
* 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
*/
probeType: {
type: Number,
default: 1
},
/**
* 点击列表是否派发click事件
*/
click: {
type: Boolean,
default: true
},
/**
* 是否开启横向滚动
*/
scrollX: {
type: Boolean,
default: false
},
/**
* 是否派发滚动事件
*/
listenScroll: {
type: Boolean,
default: false
},
/**
* 列表的数据
*/
data: {
type: Array,
default: null
},
/**
* 标识列表的数据是否为空
*/
flag: {
type: Boolean,
default: false
},
/**
* 是否派发滚动到底部的事件,用于上拉加载
*/
pullup: {
type: Boolean,
default: false
},
/**
* 是否派发顶部下拉的事件,用于下拉刷新
*/
pulldown: {
type: Boolean,
default: false
},
/**
* 是否派发列表滚动开始的事件
*/
beforeScroll: {
type: Boolean,
default: false
},
/**
* 当数据更新后,刷新scroll的延时。
*/
refreshDelay: {
type: Number,
default: 20
}
},
data() {
return {
IsRefresh: false,
IsLoading: false
};
},
mounted() {
// 保证在DOM渲染完毕后初始化better-scroll
setTimeout(() => {
this._initScroll();
}, 20);
},
methods: {
_initScroll() {
if (!this.$refs.wrapper) {
return;
}
// better-scroll的初始化
this.scroll = new BScroll(this.$refs.wrapper, {
probeType: this.probeType,
click: this.click,
scrollX: this.scrollX
});
// 是否派发滚动事件
if (this.listenScroll) {
this.scroll.on('scroll', (pos) => {
this.$emit('scroll', pos);
});
}
// 是否派发滚动到底部事件,用于上拉加载
if (this.pullup) {
this.scroll.on('scrollEnd', () => {
// 滚动到底部
if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
this.$emit('scrollToEnd');
}
});
}
// 是否派发顶部下拉事件,用于下拉刷新
if (this.pulldown) {
this.scroll.on('touchEnd', (pos) => {
// 下拉动作
if (pos.y > 50) {
this.$emit('pulldown');
}
});
}
// 是否派发列表滚动开始的事件
if (this.beforeScroll) {
this.scroll.on('beforeScrollStart', () => {
this.$emit('beforeScroll');
});
}
},
disable() {
// 代理better-scroll的disable方法
this.scroll && this.scroll.disable();
},
enable() {
// 代理better-scroll的enable方法
this.scroll && this.scroll.enable();
},
refresh() {
// 代理better-scroll的refresh方法
this.scroll && this.scroll.refresh();
},
scrollTo() {
// 代理better-scroll的scrollTo方法
this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments);
},
scrollToElement() {
// 代理better-scroll的scrollToElement方法
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments);
},
refreshTip() {
let topTip = document.querySelector('.refresh-hook');
topTip.innerText = '释放立即刷新';
this.IsRefresh = true;
},
refreshAlert(text) {
let alert = document.querySelector('.alert-hook');
text = text || '操作成功';
alert.innerHtml = text;
alert.style.display = 'block';
setTimeout(function () {
alert.style.display = 'none';
}, 1000);
},
loadData() {
let topTip = document.querySelector('.refresh-hook');
topTip.innerText = '下拉刷新';
this.IsRefresh = false;
this.refreshAlert('刷新成功');
},
shanglajiazai() {
let bottomTip = document.querySelector('.loading-hook');
bottomTip.innerText = '加载中...';
this.IsLoading = true;
setTimeout(() => {
if (!this.flag) {
bottomTip.innerText = '查看更多';
this.$emit('getData');
} else {
bottomTip.innerText = '没有更多数据';
}
this.IsLoading = false;
}, 1000);
}
},
watch: {
// 监听数据的变化,延时refreshDelay时间后调用refresh方法重新计算,保证滚动效果正常
data() {
setTimeout(() => {
this.refresh();
}, this.refreshDelay);
}
}
};
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import "../../common/stylus/mixin.styl";
.scroll-wrapper
.scroll-main
.top-tip
.refresh-top
position: absolute
top: -40px
left: 0
refreshstyle(40px)
.refresh-hook
vertical-align: middle
.bottom-tip
refreshstyle(40px)
.loading-hook
vertical-align: middle
.refresh-success
display: none
position: fixed
top: 0
left: 0
z-index: 2
width: 100%
height: 40px
line-height: 40px
text-align: center
color: #fff
font-size: 12px
background: rgba(7, 17, 27, 0.5)
</style>
refreshstyle(40px)函数是使用stylus编写的复用样式函数,代码如下:
// 上拉刷新下拉加载样式函数refreshstyle($top)
width: 100%
height: $top
z-index: 1
line-height: $top
text-align: center
color: #777
background: #f2f2f2
.icon-jiazai
display: inline-block
width: 15px
height: 15px
margin-right: 10px
vertical-align: middle
background-size: 15px 15px
background-repeat: no-repeat
background-image: url("jiazai.gif")
如何使用scroll.vue组件实现上拉加载,下拉刷新
<template><div>
<div class="header">测试上拉刷新下拉加载组件</div>
<scroll class="wrapper" ref="scrollContent"
:data="lastdata"
:pulldown="pulldown"
:pullup="pullup"
:flag="flag"
:listenScroll="listenScroll"
@pulldown="loadData"
@scroll="refreshTip"
@getData="getData"
@scrollToEnd="shanglajiazai">
<ul class="icon-wrapper">
<li class="icon-list border-1px" v-for="(item ,index) in lastdata" :key="index">
<i class="iconfont" :class="item.icon"></i>
<span class="title">{{item.name}}</span>
</li>
</ul>
</scroll>
</div>
</template>
<script type="text/ecmascript-6">
import scroll from 'components/scroll/scroll';
import { mockTableData } from 'common/js/page';
// mockTableData 函数是page.js中的一个处理数据的通用函数,这为前端模拟后台数据所写,不用care,请用实际上发出的promise请求从后端请求数据,明白原理即可。
export default {
data() {
return {
lastdata: this._initData(),
flag: false,
count: 1, // 页码
pagesize: 20, // 每页笔数
listenScroll: true,
pulldown: true,
pullup: true
};
},
created() {
this.refreshData();
},
methods: {
loadData() {
this.refreshData();
this.$refs.scrollContent.loadData();
},
refreshTip(pos) {
if (pos.y > 40) {
this.$refs.scrollContent.refreshTip();
}
},
shanglajiazai() {
this.$refs.scrollContent.shanglajiazai();
},
getData() {
this.count = this.count + 1;
let tempData = this._initData();
if (!tempData.length) {
this.flag = true;
} else {
this.lastdata = this.lastdata.concat(this._initData());
}
},
refreshData() {
this.count = 1;
this.lastdata = this._initData();
this.flag = false;
},
_initData() {
let icondata = [
{
icon: 'icon-sousuo',
name: 'icon-sousuo'
},
{
icon: 'icon-dianpu',
name: 'icon-dianpu'
},
{
icon: 'icon-huiyuanqia',
name: 'icon-huiyuanqia'
},
{
icon: 'icon-xiugaioryijian',
name: 'icon-xiugaioryijian'
},
{
icon: 'icon-shoucangxuanzhong',
name: 'icon-shoucangxuanzhong'
},
{
icon: 'icon-shoucang',
name: 'icon-shoucang'
},
{
icon: 'icon-quanbudingdan',
name: 'icon-quanbudingdan'
},
{
icon: 'icon-dianzan',
name: 'icon-dianzan'
},
{
icon: 'icon-app',
name: 'icon-app'
},
{
icon: 'icon-browser',
name: 'icon-browser'
},
{
icon: 'icon-form',
name: 'icon-form'
},
{
icon: 'icon-cart',
name: 'icon-cart'
},
{
icon: 'icon-home',
name: 'icon-home'
},
{
icon: 'icon-mine',
name: 'icon-mine'
},
{
icon: 'icon-31daifahuo',
name: 'icon-31daifahuo'
},
{
icon: 'icon-31daifukuan',
name: 'icon-31daifukuan'
},
{
icon: 'icon-31daishouhuo',
name: 'icon-31daishouhuo'
},
{
icon: 'icon-31daipingjia',
name: 'icon-31daipingjia'
},
{
icon: 'icon-tuikuantuihuo',
name: 'icon-tuikuantuihuo'
},
{
icon: 'icon-zhongtumoshi',
name: 'icon-zhongtumoshi'
},
{
icon: 'icon-xuanzekuangmoren',
name: 'icon-xuanzekuangmoren'
},
{
icon: 'icon-shanchu',
name: 'icon-shanchu'
},
{
icon: 'icon-gouwuchetianjia',
name: 'icon-gouwuchetianjia'
},
{
icon: 'icon-shouhuodizhi',
name: 'icon-shouhuodizhi'
},
{
icon: 'icon-wodefankui',
name: 'icon-wodefankui'
},
{
icon: 'icon-yijianfankui',
name: 'icon-yijianfankui'
},
{
icon: 'icon-shanchu3',
name: 'icon-shanchu3'
},
{
icon: 'icon-bianji',
name: 'icon-bianji'
},
{
icon: 'icon-huishouzhan7',
name: 'icon-huishouzhan7'
},
{
icon: 'icon-fenlei-copy',
name: 'icon-fenlei-copy'
},
{
icon: 'icon-chenggong',
name: 'icon-chenggong'
},
{
icon: 'icon-xuanzekuangxuanzhong',
name: 'icon-xuanzekuangxuanzhong'
},
{
icon: 'icon-bangzhuguanyuwomen',
name: 'icon-bangzhuguanyuwomen'
},
{
icon: 'icon-cancel-1-copy',
name: 'icon-cancel-1-copy'
},
{
icon: 'icon-chenggong1',
name: 'icon-chenggong1'
},
{
icon: 'icon-wodedaifahuo3dtouchshangpinxiangqing',
name: 'icon-wodedaifahuo3dtouchshangpinxiangqing'
},
{
icon: 'icon-lajitong',
name: 'icon-lajitong'
},
{
icon: 'icon-shangpin',
name: 'icon-shangpin'
},
{
icon: 'icon-zhifu',
name: 'icon-zhifu'
},
{
icon: 'icon-gouwu',
name: 'icon-gouwu'
},
{
icon: 'icon-faxian',
name: 'icon-faxian'
},
{
icon: 'icon-vivo',
name: 'icon-vivo'
},
{
icon: 'icon-discover',
name: 'icon-discover'
},
{
icon: 'icon-shouye1',
name: 'icon-shouye1'
},
{
icon: 'icon-yunshuzhongwuliu-xianxing',
name: 'icon-yunshuzhongwuliu-xianxing'
},
{
icon: 'icon-baoguofahuo-xianxing',
name: 'icon-baoguofahuo-xianxing'
},
{
icon: 'icon-shezhi-xianxing',
name: 'icon-shezhi-xianxing'
},
{
icon: 'icon-shouquan',
name: 'icon-shouquan'
},
{
icon: 'icon-dianzan1',
name: 'icon-dianzan1'
},
{
icon: 'icon-share_icon',
name: 'icon-share_icon'
},
{
icon: 'icon-dongtaixuanzhong',
name: 'icon-dongtaixuanzhong'
},
{
icon: 'icon-tijiao',
name: 'icon-tijiao'
},
{
icon: 'icon-VIVO',
name: 'icon-VIVO'
},
{
icon: 'icon-youjiantou',
name: 'icon-youjiantou'
},
{
icon: 'icon-zuojiantou',
name: 'icon-zuojiantou'
},
{
icon: 'icon-gouwuche',
name: 'icon-gouwuche'
},
{
icon: 'icon-dingdan',
name: 'icon-dingdan'
},
{
icon: 'icon-wode',
name: 'icon-wode'
},
{
icon: 'icon-shanchu1',
name: 'icon-shanchu1'
},
{
icon: 'icon-qijiandian',
name: 'icon-qijiandian'
},
{
icon: 'icon-shanchu2',
name: 'icon-shanchu2'
},
{
icon: 'icon-shouye2',
name: 'icon-shouye2'
},
{
icon: 'icon-shouye',
name: 'icon-shouye'
},
{
icon: 'icon-wo',
name: 'icon-wo'
},
{
icon: 'icon-shangchao',
name: 'icon-shangchao'
},
{
icon: 'icon-changyonggoupiaorenshanchu',
name: 'icon-changyonggoupiaorenshanchu'
},
{
icon: 'icon-praise',
name: 'icon-praise'
},
{
icon: 'icon-dongtai',
name: 'icon-dongtai'
},
{
icon: 'icon-shouye_xuanzhong',
name: 'icon-shouye_xuanzhong'
},
{
icon: 'icon-wode-',
name: 'icon-wode-'
},
{
icon: 'icon-gouwuche-xuanzhong',
name: 'icon-gouwuche-xuanzhong'
},
{
icon: 'icon-icon--',
name: 'icon-icon--'
},
{
icon: 'icon-fenleixuanzhong',
name: 'icon-fenleixuanzhong'
},
{
icon: 'icon-tubiaolunkuo-',
name: 'icon-tubiaolunkuo-'
},
{
icon: 'icon-tubiaolunkuo-1',
name: 'icon-tubiaolunkuo-1'
},
{
icon: 'icon-fenlei3',
name: 'icon-fenlei3'
},
{
icon: 'icon-dingdanquxiao',
name: 'icon-dingdanquxiao'
},
{
icon: 'icon-shoucangxuanzhong1',
name: 'icon-shoucangxuanzhong1'
},
{
icon: 'icon-zy_peisong',
name: 'icon-zy_peisong'
}
];
return mockTableData(this.count, this.pagesize, icondata);
}
},
components: {
scroll
}
};
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
@import "../../common/stylus/mixin.styl";
.header
width: 100%
height: 40px
line-height: 40px
text-align: center
font-size: 14px
color: rgb(147, 153, 159)
.wrapper
position: absolute
top: 40px
bottom: 55px
width: 100%
overflow: hidden
.icon-wrapper
.icon-list
width: 100%
padding: 12px 6px
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
font-size: 0
border-1px(rgba(7, 17, 27, 0.1))
.iconfont, .title
display: inline-block
vertical-align: top
line-height: 24px
.iconfont
font-size: 24px
color: mediumvioletred
.title
margin-left: 24px
font-size: 16px
color: dodgerblue
</style>
项目的实现效果如下:
最后
以上就是风趣墨镜为你收集整理的使用better-scroll配合vue实现上拉加载下拉刷新组件的全部内容,希望文章能够帮你解决使用better-scroll配合vue实现上拉加载下拉刷新组件所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复