首頁  >  文章  >  php教程  >  Vue.js實現無限載入與分頁功能開發

Vue.js實現無限載入與分頁功能開發

高洛峰
高洛峰原創
2016-12-08 09:31:592051瀏覽

本篇文章是一篇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 &#39;./components/List&#39;
 
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中加入了:


效果:

Vue.js實現無限載入與分頁功能開發

效果:


。 vue


目前我們主要的業務邏輯都是圍繞著資訊清單展開的,也就是我們所建立的List.vue。首先,我們需要取得目標數據,我選用了cnodejs.org社群的API作為範例進行寫。如果你也想用一個封裝好的ajax函式庫的話,應該這麼做:🎜🎜引入第三方JS函式庫🎜將目標JS函式庫檔案放在static資料夾中,例如我選擇的是reqwest.js,然後在index. html先引入。 🎜


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

externals: {
 &#39;reqwest&#39;: &#39;reqwest&#39;
}
   
这样我们在我们的项目中,就可以随时加载第三方库了。
import reqwest from &#39;reqwest&#39;

写个API接口
在这个例子中,我们只需要调用文章列表这一个接口,但是实际项目中,可能你需要调用很多接口,而这些接口又会在多个组件中被用到。那么调用接口的逻辑四散在各个组件中肯定是不好的,想象一下对方的url发生了变化,你就得在无数个组件中一个个检查是否要修改。

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

import reqwest from &#39;reqwest&#39;
 
const domain = &#39;https://cnodejs.org/api/v1/topics&#39;
 
export default {
 getList (data, callback) {
 reqwest({
 url: domain,
 data: data
 })
 .then(val => callback(null, val))
 .catch(e => callback(e))
 }
}

   

这样我们就拥有了一个获取新闻列表的API:getList。

编写组件

我们用一个

    作为新闻列表,内部的每一个
  1. 就是一条新闻,其中包括标题、时间和作者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 &#39;../api/news&#39;
     
    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中的修改为:


    再为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 }}显示页数。

    GO NEXTCURRENT:{{page}}

    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? &#39;up-start&#39;:&#39;down-start&#39;">
     <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 &#39;vue&#39;
    import App from &#39;./App&#39;
    import VueRouter from &#39;VueRouter&#39;
     
    Vue.use(VueRouter)
     
    const router = new VueRouter()
    router.map({
     &#39;/page/:pageNum&#39;: {
     name: &#39;page&#39;,
     component: {}
     }
    })
     
    router.redirect({
     &#39;/&#39;: &#39;/page/1&#39;
    })
     
    router.beforeEach((transition) => {
     if (transition.to.path !== &#39;/page/0&#39;) {
     transition.next()
     } else {
     transition.abort()
     }
    })
     
    router.start(App, &#39;app&#39;)

       

    首先定义了一个名为page的具名路径。之后将所有目标路径为'/',也就是初始页的请求,重定向到'/page/1'上保证一致性。最后再在每次路由执行之前做一个判断,如果到了'/page/0'这样的非法路径上,就不执行transition.next()。

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


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn