Vue.js 开辟实践:完成精致的无穷加载与分页功用

本篇文章是一篇Vue.js的教程,目的在于用一种罕见的营业场景——分页/无穷加载,协助读者更好的明白Vue.js中的一些设想头脑。与许多Todo List类的入门教程比拟,更全面的展现运用Vue.js完成一个需求的思索历程;与一些构建大型运用的高阶教程比拟,又更专注于一些细碎细节的完成,轻易读者疾速掌握、致用。

需求剖析

当一个页面中信息量过大时(比方一个消息列表中有200条消息须要展现),就会发作题目,比方:

  • 数据量过大,影响加载速率

  • 用户体验差,很难定位到之前本身看过的某篇文章

  • 扩大性差,假如200条变成2000条或许更多

所以罕见的处理思绪就是至底时加载数据或许分页展现。无穷加载的完成历程类似于:

  1. ajax类要领猎取数据

  2. 数据存入当地数组

  3. 数组中的每条数据对应插进去一个HTML模板片断中

  4. 将HTML片断append到节点中

前端分页的完成历程类似于:

  1. ajax类要领猎取数据

  2. 数据替代当地数组

  3. 数组中的每条数据对应插进去一个HTML模板片断中

  4. 清空节点后将HTML片断append到节点中

每每修正或许保护代码时,我们会发明衬着HTML和插进去部份是比较烦人的。因为我们须要将HTML拼接成字符串,在对应的位置插进去数据,每每就是一段异常长的字符串,今后想要加个class都费力。es6的模板字符串让这个状况有所好转,然则依旧有瑕疵(比方现实编写时没法HTML代码高亮)。

同时我们还须要写不少for或许forEach去轮回数组,再敕令式的append,假如这段代码片断有一些庞杂的交互,能够还须要经由过程事宜代办绑定一堆要领。

假如在完成这类营业时,你也遇到过上述的题目,那末你就会发明Vue真是太coooooool了,let’s vue!

新建一个Vue.js项目

强烈推荐运用vue-cli来新建一个项目。

一最先你能够会以为用node.js和npm装置一大堆库,生成了一些你不太相识的目次和设置文件,一写代码还会跳出一堆eslint的提醒。然则这相对物有所值,因为如许的一个模板能够帮你更好的明白Vue.js构造文件的思绪,而且当你顺应今后,你会发明这些条条框框极大地加快了你的开辟效力。

在这次的教程中,我们新建了一个名叫loadmore的项目,详细的新建项目流程能够参照官网教程的装置一节。

规划页面构造

为了合营教程的逐渐深切,我先从完成加载更多功用入手。为了和今后的分页保持一致,我的页面预备由两部份构成,一是信息列表,二是底部的一个加载更多的按钮,我将他们都放在App.vue这个根组件中。

<template>
  <div id="app">
    <list></list>
    <a class="button" @click="next" >GO NEXT</a>
  </div>
</template>

<script>
import List from './components/List'

export default {
  components: {
    List
  },
  data () {
    return {
      ...
    }
  },
  methods: {
    next () {
      ...
    }
  }
}
</script>

<style scoped>
  .button {
    display: block;
    width: 100%;
    background: #212121;
    color: #fff;
    font-weight: bold;
    text-align: center;
    padding: 1em;
    cursor: pointer;
    text-decoration: none;
  }
  .button span {
    margin-left: 2em;
    font-size: .5rem;
    color: #d6d6d6;
  }
</style>

在这个历程当中,我们根据Vue的设想头脑有了以下思绪:

  1. 在信息列表中,我们会完成我们上文中提到的几个步骤,而这些步骤都只和信息列表本身有关,与Next按钮间唯一的联络就是Next点击后须要触发信息列表去猎取,而这能够经由过程props通报。所以我们把列表及其本身营业逻辑、款式都放在List.vue这个组件中。

  2. 我们为按钮定义了一些基本的款式,然则我们用的css挑选器就是一个.button类名,能够会和别的组件中的.button款式争执,所以我们到场了一个scoped属性,让App.vue中的style款式只作用于这个组件内部。
    注重:scoped并不会影响css的作用优先级,运用scoped不代表不会被外部款式表掩盖。

  3. 我们想引入一些基本款式,比方reset.css。假如在项目中运用了sass之类的言语,那末能够将对应的外部sass文件放在assets文件夹中,经由过程import引入。一般的css能够直接写在一个不加scoped属性的组件中,然则假如你肯定这个款式表不会被频仍修改,那末也能够作为第三方静态资本引入index.html中。比方这个例子中,我在index.html中到场了:

<link rel="stylesheet" href="./static/reset.css">

结果:

《Vue.js 开辟实践:完成精致的无穷加载与分页功用》

完成List.vue

现在我们重要的营业逻辑都是缭绕信息列表睁开的,也就是我们竖立的List.vue。

起首,我们须要猎取目的数据,我选用了cnodejs.org社区的API作为例子举行编写。假如你也想用一个封装好的ajax库的话,应当这么做:

引入第三方JS库

将目的JS库文件放在static文件夹中,比方我挑选的是reqwest.js,然后在index.html先引入。

<script src="./static/reqwest.min.js"></script>

然后在build设置文件夹中,修正webpack.base.conf.js,export externals属性:

externals: {
  'reqwest': 'reqwest'
}

如许我们在我们的项目中,就能够随时加载第三方库了。

import reqwest from 'reqwest'

写个API接口

在这个例子中,我们只须要挪用文章列表这一个接口,然则现实项目中,能够你须要挪用许多接口,而这些接口又会在多个组件中被用到。那末挪用接口的逻辑四散在各个组件中肯定是不好的,设想一下对方的url发作了变化,你就得在无数个组件中一个个搜检是不是要修正。

所以我在src文件夹中新建了一个api文件夹,用于寄存各种API接口。当前例子中,要猎取的是消息列表,所以新建一个news.js文件:

import reqwest from 'reqwest'

const domain = 'https://cnodejs.org/api/v1/topics'

export default {
  getList (data, callback) {
    reqwest({
      url: domain,
      data: data
    })
    .then(val => callback(null, val))
    .catch(e => callback(e))
  }
}

如许我们就具有了一个猎取消息列表的API:getList。

编写组件

我们用一个<ol>作为消息列表,内部的每个<li>就是一条消息,个中包含题目、时刻和作者3个信息。

在data中,我们用一个名为list的数组来贮存消息列表的数据,一最先固然是空的。我们再在data中设置一个名为limit的值,用来掌握每页加载若干条数据,作为参数传给getList这个API。

因而我们的template部份是如许的(到场了一些style美化款式):

<template>
  <ol>
    <li v-for="news of list">
      <p class="title">{{ news.title }}</p>
      <p class="date">{{ news.create_at }}</p>
      <p class="author">By: {{ news.author.loginname }}</p>
    </li>
  </ol>
</template>

<style scoped>
  ol {
    margin-left: 2rem;
    list-style: outside decimal;
  }
  li {
    line-height: 1.5;
    padding: 1rem;
    border-bottom: 1px solid #b6b6b6;
  }
  .title {
    font-weight: bold;
    font-size: 1.3rem;
  }
  .date {
    font-size: .8rem;
    color: #d6d6d6;
  }
</style>

今后我们明显须要运用getList来猎取数据,不过先想一想我们会在哪几个处所运用呢?起首,我们须要在组件最先衬着时自动猎取一次列表,添补基本内容。其次,我们在每次点击APP.vue中的Next按钮时也须要猎取新的列表。

所以我们在methods中定义一个get要领,胜利猎取到数据后,就把猎取的数组拼接到当前list数组后,从而完成了加载更多。

沿着这个思绪,再想一想get要领须要的参数,一个是包含了page和limit两个属性的对象,另一个是回调函数。回调函数我们已说过,只须要拼接数组即可,因而只剩下末了一个page参数还没设置。

在初始化的时刻,page的值应当为1,默许是第一页内容。今后page的值只由Next按钮转变,所以我们让page经由过程props猎取App.vue中传来的page值。

末了则是补充get要领触发的前提。一是在组件的性命周期函数created中挪用this.get()猎取初始内容,另一是在page值变化时对应猎取,所以我们watch了page属性,当其变化时,挪用this.get()。

末了List.vue的script长如许:

<script>
import news from '../api/news'

export default {
  data () {
    return {
      list: [],
      limit: 10
    }
  },
  props: {
    page: {
      type: Number,
      default: 1
    }
  },
  created () {
    this.get()
  },
  watch: {
    page (val) {
      this.get()
    }
  },
  methods: {
    get () {
      news.getList({
        page: this.page,
        limit: this.limit
      }, (err, list) => {
        if (err) {
          console.log(err)
        } else {
          list.data.forEach((data) => {
            const d = new Date(data.create_at)
            data.create_at = `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
          })
          this.list = this.list.concat(list.data)
        }
      })
    }
  }
}
</script>

同时我们将App.vue中的<list>修正为:

<list :page="page"></list>

再为page在App.vue中增加一个初始值以及对应的要领next:

data () {
  return {
    page: 1
  }
},
methods: {
  next () {
    this.page++
  }
}

如许我们就已完成了加载更多的功用。

《Vue.js 开辟实践:完成精致的无穷加载与分页功用》

改写为分页

因为之前我们的思绪异常清楚,代码构造也很清楚明了,所以改写起来会异常简朴,只须要将List.vue中拼接数组改成赋值数组就能够了:

// 通例loadmore
// this.list = this.list.concat(list.data)
// 分页
this.list = list.data

就这么简朴的一行就完成了功用的转变,这就是Vue.js中中心的数据驱动视图的威力。固然,接下来我们还要做点更cooooool的。

增加功用

因为分页替代了本来的数组,所以仅仅一个Next按钮不够用了,我们还须要一个Previous按钮返回上一页。一样的,也给Previous按钮绑定一个previous要领,除了用this.page–转变page的值之外,还须要对this.page === 1的边界前提举行一个推断。

同时为了轻易晓得我们当前的页数,在按钮中,到场{{ page }}显现页数。

<a class="button" @click="next" >GO NEXT<span>CURRENT:{{page}}</span></a>

transition动画

编写和完美功用的历程当中,已充分体现了Vue.js清楚和方便的一面,接下来继承看看别的好用的功用,起首就是transition动画。

为了展现transition的威力,起首我找到了一个模拟的对象:lavalamp.js(Demo地点)。

在Demo中能够看到页面以一种异常文雅的动画过渡完成了切换内容的历程,其本身是用JQuery+CSS动画完成的,我预备用Vue.js举行改写。

起首进修了一下原作者的完成思绪今后,发明是将一个div作为loader,position设定为fixed。当翻页时,根据点击的按钮差别,loader从顶部或许底部扩大高度,到达100%。数据加载终了后,再摺叠高度,终究隐蔽。

那末开端的思绪以下:

  1. 增加一个loader,最小高度与按钮一致,背景同为黑色,让过渡显得更天然。

  2. loader高度须要到达一个屏幕的高度,所以设置html和body的height为100%。

  3. 须要有一个值,作为loader是不是显现的根据,我定为finish,其默许值值为true,经由过程给loader增加v-show=”!finish”来掌握其显现。

  4. 在next和previous要领中增加this.finish = false触发loader的显现。

  5. 在App.vue和List.vue竖立一个双向的props属性绑定至finish,当List.vue中的get要领实行终了后,经由过程props将App.vue中的finish设定为true,隐蔽loader。

  6. 给loader增加一个transition。因为动画分为顶部睁开和底部睁开两种,所以运用动态的transition为其指定准确的transition称号。

  7. 新增一个值up,用于推断动画从哪一个方向最先,其默许值为false。在previous要领中,实行this.up = true,反之在next要领中,则实行this.up = false。

根据思绪,写出的loader应当是如许的(style等款式设定在末了一致展现):

<div id="loader" v-show="!finish" :transition="up? 'up-start':'down-start'">
  <span>Loading</span>
</div>

能够看到我设定了up-start和down-start两种transition体式格局,对应的css动画代码以下:

.down-start-transition {
    bottom: 0;
    height: 100%;
  }
  .down-start-enter {
    animation: expand .5s 1 cubic-bezier(0, 1, 0, 1) both;
  }
  .down-start-leave {
    animation: collapse .5s 1 cubic-bezier(0, 1, 0, 1) both;
    top: 0;
    bottom: auto;
  }
  .up-start-transition {
    top: 0;
    height: 100%;
  }
  .up-start-enter {
    animation: expand .5s 1 cubic-bezier(0, 1, 0, 1) both;
  }
  .up-start-leave {
    animation: collapse .5s 1 cubic-bezier(0, 1, 0, 1) both;
    top: auto;
    bottom: 0;
  }
  @keyframes expand {
    0% {
      height: 3em;
      transform: translate3d(0, 0, 0);
    }
    100% {
      height: 100%;
      transform: translate3d(0, 0, 0);
    }
  }
  @keyframes collapse {
    0% {
      height: 100%;
      transform: translate3d(0, 0, 0);
    }
    100% {
      height: 3em;
      transform: translate3d(0, 0, 0);
    }
  }

设置了expand和collapse两个animation,再在transition的各个性命周期钩子中做对应的绑定,就到达了和lavalamp.js相靠近的结果。

为了保证动画能实行完全,在List.vue的get要领实行完今后,还运用了一个setTimeout定时器让finish延时0.5秒变成true。

优化体验

动画结果完成今后,现实运用时发明lavalamp.js另有个奇妙地设想,就是点击Previous后,页面前去底部,反之点击Next后则前去顶部。

完成后者并不庞杂,在next要领中到场以下一行代码调解位置即可:

document.body.scrollTop = 0

previous前去底部则稍微庞杂一点,因为猎取到数据今后,页面高度会发作转变,假如在previous中实行scrollTop的转变,有能够会涌现新的内容添补后高度变长,页面不究竟的状况。

所以我watch了finish的值,仅当点击按钮为previous且finish变化为false至true时前去底部,代码以下:

watch: {
  finish (val, oldVal) {
    if (!oldVal && val && this.up) {
      document.body.scrollTop = document.body.scrollHeight
    }
  }
}

前端路由

完成以上内容今后,发明不管翻到第几页,一旦革新,就会回到第一页。vue-router就是为处理这类题目而生的。

起首我们引入VueRouter,体式格局能够参考上文中的“引入第三方JS库”。然后在main.js对路由划定规矩举行一些设置。

我们的思绪包含:

  1. 我们须要在url上反映出当前所处的页数。

  2. url中的页数应当与一切组件中的page值保持一致。

  3. 点击Next和Previous按钮要跳转到对应的url去。

  4. 在这个例子中我们没有router-view。

因而main.js的设置以下:

import Vue from 'vue'
import App from './App'
import VueRouter from 'VueRouter'

Vue.use(VueRouter)

const router = new VueRouter()
router.map({
  '/page/:pageNum': {
    name: 'page',
    component: {}
  }
})

router.redirect({
  '/': '/page/1'
})

router.beforeEach((transition) => {
  if (transition.to.path !== '/page/0') {
    transition.next()
  } else {
    transition.abort()
  }
})

router.start(App, 'app')

起首定义了一个名为page的签字途径。今后将一切目的途径为’/’,也就是初始页的要求,重定向到’/page/1’上保证一致性。末了再在每次路由实行之前做一个推断,假如到了’/page/0’如许的不法途径上,就不实行transition.next()。

根据之前的思绪,在App.vue中,猎取路由对象的参数值,赋值给page。同时给两个按钮增加对应的v-link。

终究的demo地点
Github堆栈

    原文作者:aryu
    原文地址: https://segmentfault.com/a/1190000005351971
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞