>  기사  >  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 조각을 노드에 추가합니다.

프런트 엔드 페이징 구현 프로세스는 다음과 유사합니다.

1. 데이터를 얻기 위한 Ajax 클래스 메소드

2. 로컬 배열을 데이터로 대체합니다

3. 배열의 각 데이터 조각은 HTML 템플릿 조각에 삽입됩니다

4. 노드를 지운 후 노드에 HTML 조각을 추가합니다

코드를 수정하거나 유지 관리할 때 HTML을 렌더링하고 부품을 삽입하는 것이 더 짜증나는 경우가 많습니다. HTML을 문자열로 이어붙여 해당 위치에 데이터를 삽입해야 하는데, 문자열이 매우 긴 경우가 많고, 나중에 클래스를 추가하기도 어렵기 때문입니다. es6의 템플릿 문자열은 이러한 상황을 개선했지만 여전히 결함이 있습니다(예: 실제 작성 중에 HTML 코드를 강조 표시할 수 없음). 동시에 배열을 반복하기 위해 for 또는 forEach를 많이 작성하고 명령적으로 추가해야 합니다. 이 코드 조각에 복잡한 상호 작용이 있는 경우 이벤트 프록시를 통해 여러 메서드를 바인딩해야 할 수도 있습니다.

이 유형의 비즈니스를 완료할 때 위의 문제에 직면했다면 Vue가 정말 멋지다는 것을 알게 될 것입니다. vue하자!

새 Vue.js 프로젝트 만들기

새 프로젝트를 생성하려면 vue-cli를 사용하는 것이 좋습니다.

처음에는 node.js와 npm을 사용하여 많은 라이브러리를 설치하면 잘 알지 못하는 일부 디렉터리와 구성 파일이 생성되고 다음과 같은 여러 eslint 프롬프트가 나타날 것이라고 생각할 수 있습니다. 코드를 작성하자마자. 그러나 그러한 템플릿은 Vue.js에서 파일 구성에 대한 아이디어를 더 잘 이해하는 데 도움이 될 수 있고, 적용하면 이러한 규칙과 규정이 개발 효율성을 크게 향상시키는 것을 발견할 수 있기 때문에 확실히 돈의 가치가 있습니다.

이 튜토리얼에서는 loadmore라는 새 프로젝트를 만들었습니다. 구체적인 새 프로젝트 프로세스는 공식 웹사이트 튜토리얼의 설치 섹션을 참조하세요.

레이아웃 페이지 구조

튜토리얼의 점진적인 심화에 협조하기 위해 Load More 기능을 완료하는 것부터 시작하겠습니다. 후속 페이징과 일관성을 유지하기 위해 내 페이지는 두 부분으로 구성되도록 준비되었습니다. 하나는 정보 목록이고 다른 하나는 하단에 있는 추가 로드 버튼입니다. 둘 다 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. 위에서 언급한 여러 단계를 완료하며 이러한 단계는 정보 목록 자체에만 관련됩니다. 다음 버튼과의 유일한 연결은 다음을 클릭한 후 정보 목록을 가져오기 위해 트리거되어야 하며 이를 통해 전달될 수 있다는 것입니다. 소품. 그래서 우리는 List.vue 구성 요소에 목록과 자체 비즈니스 논리 및 스타일을 넣었습니다.

2. 버튼에 대한 몇 가지 기본 스타일을 정의했지만 우리가 사용하는 CSS 선택기는 .button 클래스 이름이므로 다른 구성 요소의 .button 스타일과 충돌할 수 있으므로 범위가 지정된 속성을 추가했습니다. App.vue의 스타일은 이 구성 요소 내부에만 적용됩니다.

참고: 범위 지정은 CSS의 우선순위에 영향을 주지 않습니다. 범위 지정을 사용한다고 해서 외부 스타일 시트가 재정의되지 않는다는 의미는 아닙니다.

3. Reset.css와 같은 몇 가지 기본 스타일을 소개하려고 합니다. 프로젝트에서 sass와 같은 언어를 사용하는 경우 해당 외부 sass 파일을 자산 폴더에 배치하고 가져오기를 통해 도입할 수 있습니다. 일반 CSS는 범위가 지정된 속성 없이 구성 요소에 직접 작성할 수 있지만 이 스타일 시트가 자주 변경되지 않을 것이라고 확신하는 경우 이를 index.html에 타사 정적 리소스로 도입할 수도 있습니다. 예를 들어, 이 예에서는 다음을 추가했습니다.


효과:

Vue.js는 무한 로딩 및 페이징 기능 개발을 구현합니다.

전체 List.vue

현재 우리의 주요 비즈니스 로직은 정보 목록을 중심으로 이루어집니다. 생성되었습니다. 먼저, 대상 데이터를 얻어야 합니다. 저는 cnodejs.org 커뮤니티의 API를 예시로 선택했습니다. 캡슐화된 Ajax 라이브러리도 사용하려면 다음을 수행해야 합니다.

타사 JS 라이브러리 소개
대상 JS 라이브러리 파일을 static 폴더에 넣습니다. 예를 들어 저는 reqwest를 선택했습니다. Node.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으로 문의하세요.