使用Vue观察者实现一个简单异步无限滚动效果
无限滚动(Infinite Scroll)是一种很常见的用户体验模式,它建议用户在Web页面或应用程序加载显示很少的内容。当用户开始向下滚动页面时,会加载更多内容。这些内容是通过向负责提供内容的服务器发出请求来异步加载的。在这篇文章中,我将讨论JavaScript的异步操作以及Vue如何实现无限滚动效果。在这个过程中,我们将看到一个使用无限增发动的简单页面。
理解异步操作
在程序中编写一段同步代码,比如下面的例子,有两行代码:L1
和L2
。如果L1
未结速,L2
是不会执行的:
console.log('quavo');
console.log('takeoff');
通常情况下,我们会看到上面的代码执行的顺序,那是因为$http.get
请求需要一些时间才能从our_url
获取data
,JavaScript并不会花时间去等,而是在等待$http.get
时执行下一行代码。完成它所做的事情,这样就可以在控制台上进行日志记录。异步写代码的方法还包括:
setTimeout()
函数,它可以先执行后面的一些事情,然后再执行setTimeout()
中的代码。比如:
console.log('我先执行')
setTimeout(() => {
console.log('是的,我等待3s后才执行')
},3000)
console.log('对了,我会第二个执行')
高阶函数(也称为回调函数)是作为参数传递给其他函数的函数。让我们假设有一个名为function X
的回调函数,它被当做一个参数传递给另一个函数function Y
。最终,函数X
被执行或调用内部Y
函数。比如下面的代码:
function Y(X) {
$.get("our_url", X);
}
来看一个实例:
var add = function (a, b) {
return a + b;
}
function math (func, array) {
return func(array[0] , array[1]);
}
console.log(math(add, [1, 2])) // => 3
上面的例子中传进去的add
是一个参数,而在return
的时候刚是一个函数。
高阶函数存在于不同的模式中,很有可能你在不知道的情况下就使用它们。比如window.onclick
、setTimeout()
和setInterval()
。除此之外,还有一些常见的高阶函数例子。比如jQuery ajax
回调函数:
$.ajax({
url: '//localhost:8000/api/v1/entry/1',
type: 'GET',
dataType: 'json',
success: function (data) {
// success 接收回调函数
console.log(data)
}
})
setTimeout()
和setInterval()
这样的计时器函数也是高阶函数:
setTimeout(function (){
console.log('是的,我会在3s后执行')
},3000)
var i = 0;
setInterval(function (){
i++;
console.log(`我现在的值是:${i}`)
}, 100)
在数组中的sort()
、map()
、reduce()
和filter()
等函数都是高阶函数的示例:
var arr = [2, 8, 20, 5, 17, 38, 21];
// 升序
arr.sort(function (x, y) {
return x - y
})
// 降序
arr.sort(function (x, y) {
return y - x
})
// 数据所有元素进行求平方得到新数组
arr.map(function (item) {
return Math.pow(item, 2)
})
// 数组求和
arr.reduce(function (x, y) {
return x + y
})
// 过滤掉数组中的偶数,只留奇数,返回一个新数组
arr.filter(function (x) {
return x % 2 !== 0
})
有关于高阶函数更多介绍,可以阅读下面的内容:
- JavaScript 高阶函数介绍
- 高阶函数(Higher-order function)
- JavaScript高阶函数
- Higher-Order Functions
- Higher Order Functions
- Higher-Order Functions in JavaScript
- Higher order functions
- Understand JavaScript Callback Functions and Use Them
什么是观察者
Vue观察者允许我们在更改数据时执行异步操作。它们就像是在Vue实例中对数据做更改,而视图做出相应的反应。在我们的Vue实例中,观察者用watch
关键词表示,并因此被使用:
let app = new Vue({
el: '#app',
data () {
return {
// 和view绑定的数据
}
},
watch: {
// 将在app中使用的异步操作
}
})
扩展观察者的异步操作
让我们看看Vue如何使用观察者来监视异步操作。使用Vue构建一个具有无限滚动特性的应用程序,一旦用户到达页面的底部,就会执行GET
请求,并检索更多的数据。这篇文章的示例是来自于@Sarah Drasner的原始案例(我是她的超级粉丝)。接下来看它是如何工作的。
首先使用<script>
标签导入必要的库。在这里,我们将导航Vue和Axios,这是一个基于HTTP客户端的浏览器。
<head>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
如果你在Codepen上写的话,可以直接在JavaScript设置中导入相关的脚本,如下图所示:
创建一个新的Vue实例:
let app = new Vue({
// el属性是一个挂载器,指向index.html中的#app的DOM元素
el: '#app',
})
创建data()
函数,并附加需要绑定到DOM的数据属性。最初我们是不会在页面的底部,因此bottom
的值设置为false
。另外设置beers
属性,先设置为一个空数组。
let app = new Vue({
el: '#app',
data () {
return {
bottom: false,
beers: []
}
}
})
接下来使用methods
属性创建所需要的方法。methods
允许我们创建函数,并将事件绑定到这些函数以及处理相关的事件。在bottomVisible()
函数中,我们使用三个只读属性手动创建无限滚动相关的特性:
scrollY
:返回滚动条距离viewport
顶部边缘的Y
坐标。如果没有离开viewport
,返回的值为0
clientHeight
:无素可视区高度,包括padding
,但不包括水平滚动条高度、border
或margin
scrollHeight
:元素内容的高度,包括由于溢出在屏幕上不可见的内容
有关于这方面的相关属性的介绍,可以阅读前段时间整理的一篇笔记《视口宽高、位置与滚动高度》。
let app = new Vue({
el: '#app',
data () {
return {
bottom: false,
beers: []
}
},
methods: {
bottomVisible () {
const scrollY = window.scrollY
const visible = document.documentElement.clientHeight
const pageHeight = document.documentElement.scrollHeight
const bottomOfPage = visible + scrollY >= pageHeight
return bottomOfPage || pageHeight < visible
}
}
})
在methods
中继续添加另一个函数addBeer()
,我们使用Axios执行GET
请求。使用Promises
和callback
,创建一个apiInfo
对象,并从API中调用检索值传给它。我们的Vue实例中的每个函数都可以使用this
访问data
属性。
let app = new Vue({
el: '#app',
data () {
return {
bottom: false,
beers: []
}
},
methods: {
bottomVisible () {
const scrollY = window.scrollY
const visible = document.documentElement.clientHeight
const pageHeight = document.documentElement.scrollHeight
const bottomOfPage = visible + scrollY >= pageHeight
return bottomOfPage || pageHeight < visible
},
addBeer () {
axios.get('https://api.punkapi.com/v2/beers/random')
.then(response => {
let api = response.data[0]
let apiInfo = {
name: api.name,
desc: api.description,
img: api.image_url,
tips: api.brewers_tips,
tagline: api.tagline,
food: api.food_pairing
}
this.beers.push(apiInfo)
if (this.bottomVisible()) {
this.addBeer()
}
})
}
}
})
有关于Vue中的
methods
相关的知识可以阅读前段时间整理的相关学习笔记《Vue的Methods》、《Vue的Methods和事件处理》和《在Vue中何时使用方法、计算属性或观察者》。
在watch
属性中添加相应的观察者,用来监视应用程序状态的变化,并相应的更新DOM:
let app = new Vue({
el: '#app',
data () {
return {
bottom: false,
beers: []
}
},
methods: {
bottomVisible () {
const scrollY = window.scrollY
const visible = document.documentElement.clientHeight
const pageHeight = document.documentElement.scrollHeight
const bottomOfPage = visible + scrollY >= pageHeight
return bottomOfPage || pageHeight < visible
},
addBeer () {
axios.get('https://api.punkapi.com/v2/beers/random')
.then(response => {
let api = response.data[0]
let apiInfo = {
name: api.name,
desc: api.description,
img: api.image_url,
tips: api.brewers_tips,
tagline: api.tagline,
food: api.food_pairing
}
this.beers.push(apiInfo)
if (this.bottomVisible()) {
this.addBeer()
}
})
}
},
watch: {
bottom (bottom) {
if (bottom) {
this.addBeer()
}
}
}
})
有关于Vue中的观察者相关的知识,可以阅读前段时间整理的学习笔记:《Vue的观察者》。
接下来再使用Vue的生命周期的钩子created
添加一个scroll
滚动事件,每次调用bottomVisible()
函数时触发一个滚动事件。为了实现无限滚动特性,将data
函数中的bottom
值设置为bottomVisible()
函数。created
钩子允许我们访问反应性数据和Vue实例中的函数。
let app = new Vue({
el: '#app',
data () {
return {
bottom: false,
beers: []
}
},
methods: {
bottomVisible () {
const scrollY = window.scrollY
const visible = document.documentElement.clientHeight
const pageHeight = document.documentElement.scrollHeight
const bottomOfPage = visible + scrollY >= pageHeight
return bottomOfPage || pageHeight < visible
},
addBeer () {
axios.get('https://api.punkapi.com/v2/beers/random')
.then(response => {
let api = response.data[0]
let apiInfo = {
name: api.name,
desc: api.description,
img: api.image_url,
tips: api.brewers_tips,
tagline: api.tagline,
food: api.food_pairing
}
this.beers.push(apiInfo)
if (this.bottomVisible()) {
this.addBeer()
}
})
}
},
watch: {
bottom (bottom) {
if (bottom) {
this.addBeer()
}
}
},
created () {
window.addEventListener('scroll', () => {
this.bottom = this.bottomVisible()
})
this.addBeer()
}
})
有关于Vue实例和生命周期相关的知识可以阅读前段时间整理的学习笔记《Vue实例和生命周期》。
现在把注意力集中到DOM中,将使用Vue指令让DOM和Vue实例之间实现数据的双向绑定:
v-if
:根据条件的布尔值,有条件的渲染DOM元素v-for
:基于数组循环遍历出项目列表,比如beer in beers
,其中beer
是被迭代的数组元素的别名
有关于Vue的指令相关的介绍,可以阅读:
示例中的DOM这样写:
<div id="app">
<section>
<h1>Make yourself some Punk Beers</h1>
<!-- beers数组值为空时,显示正在加载中的状态 -->
<div class="beer-container">
<div v-if="beers.length === 0" class="loading">Loading...</div>
<!-- 对beers数组进行迭代 -->
<div v-for="beer in beers" class="beer-contain">
<div class="beer-img">
<img :src="beer.img" height="350" />
</div>
<div class="beer-info">
<h2>{{ beer.name }}</h2>
<div class="beer-description">
<p><span class="bright">Description:</span> {{ beer.desc }}</p>
</div>
</div>
</div>
</div>
</section>
</div>
这个时候你的页面可以看到这样的结果:
因为还没有添加任何样式,看上去丑丑的。如果添加了样式之后,就可以看到下面这样的效果:
注意,我在@Sarah Drasner的示例上删除了一些字段,只是为了样式上看上去好看一点。原始示例的效果和源码,点击这里可以看到。
这个时候你滚动到页面的底部的时候就可以无限加载内容。这个效果也就是我们所说的无限滚动效果:
总结
有人可能会问,为什么我们不直接使用Vue的computed
属性呢?原因是computed
属性是同步的,必须返回一个值。当执行类似timeout
函数之类的异步操作,或者像上面示例中GET
请求时,最好使用Vue的watch
,因为Vue会侦听函数的返回值。使用事件监听器也很酷,但这些都有手工处理事件和调用方法而不是只监听数据更改的缺点。无论如何,当你看到或想要在Vue中使用它们时,你现在知道如何处理异步操作了吧。
特别声明,本文整个思路是跟着@Chris Nwamba的博文《Simple Asynchronous Infinite Scroll with Vue Watchers》学习整理的。文章有关于Vue的示例代码,都源于此文。
由说作者是Vue相关的初学者,如果文章中有不对之处,还请各路大婶拍正。如果你有更好的建议或者想法,欢迎在下面的评论中与我们一起分享。
如需转载,烦请注明出处:https://www.fedev.cn/vue/simple-asynchronous-infinite-scroll-with-vue-watchers.htmlNike Huaraches Are Hyped