我们的前端页面大致上整理完毕,下面进入对数据的操作了,不然不知道怎么结合数据岂不是瞎玩么。
有一些么有后台接口经验的前端同学也不要慌,咱们搞个简单的,也不要自己提供接口,网上都有写好的。
http://jsonplaceholder.typicode.com/
这个网站,提供了公共的一些可以访问的接口
我们就用这个网站的接口了,直接浏览器访问就有返回数据
- 获取10个用户:
http://jsonplaceholder.typicode.com/users
- 获取单个用户:
http://jsonplaceholder.typicode.com/users/1
- 获取100个post:
http://jsonplaceholder.typicode.com/posts
- 获取单个post:
http://jsonplaceholder.typicode.com/posts/1
我们改造一下users的列表样式,让他好看点。然后实现一下下拉刷新和滑动到底部加载更多数据。
这里用到的2个控件:van-list
和van-pull-refresh
,还有网络请求插件axios。
1. axios
安装:npm i -S axios
使用axios是超级简单的,直接import,然后axios.get或者post就好了。但是实际项目中肯定不能这么简单的处理,代码很多重复,而且不方便管理。咱们自己包装一下。
之前在vue手撸移动端后台(4)项目结构以及首页中咱们划分了api有一个单独的目录,咱们的包装文件就放这个下面src/api
下新建文件axios.js。
1.1 引入axios
// 页面的顶部第一行,引入axios
import axios from 'axios'
1.2 配置axios
既然是要包装,肯定不能直接就export了,做一些初始化配置
const service = axios.create({
baseURL:
process.env.NODE_ENV === 'production'
? '你的正式环境的域名api地址/vue/api'
: 'http://jsonplaceholder.typicode.com/',
timeout: 5000//超时时间为5s,超过5s没有返回数据就结束请求。
})
看上面的配置,咱们依据项目环境切换api的请求地址,所以当切换环境后,需要修改的地方就很少,不容易出错
1.3 token?
还有个问题,现在都是前后端分离的项目,为了保证接口调用验证用户,一般会用到token技术,axios怎么加token呢【如果不理解token也没关系,后端告诉你用户登录后会传回一个字符串token,存在前端,每次访问接口就带上这个token来】?
显然不可能每次axios请求都自己加token吧,还要从存储获取这个token,让后组装到header,传回去。没关系,axios的前置拦截器可以帮我们实现自动加token
// Add a request interceptor 在发送请求之前做一些处理, 比如加入token之类的
service.interceptors.request.use(
function(config) {
// 这里插入代码, 加入token,
return config
},
function(error) {//错误处理
return Promise.reject(error)
}
)
1.4 错误处理【不需要可以略过】
既然有token处理,那么如果超时或者网络异常了,怎么抓取到错误并给一个比较好的提示呢?这时就用到了axios的后置处理,比如我们希望请求不成功的时候,可以重复发送请求
// Add a response interceptor
//设置全局的请求次数,重试的时间间隔
service.defaults.retry = 0 //本项目关闭重复请求
service.defaults.retryDelay = 1000 //间隔时间
/**
* 加入重新请求方法, 默认关闭
*/
service.interceptors.response.use(
function(response) {
// 请求返回处理,比如各类状态处理的, token过期等. 未登录等...
// response中返回了各种http状态,可以依据返回进行提示操作。
return response
},
function axiosRetryInterceptor(err) {
let config = err.config
// If config does not exist or the retry option is not set, reject
if (!config || !config.retry) {
return Promise.reject(err)
}
// Set the variable for keeping track of the retry count
config.__retryCount = config.__retryCount || 0
// Check if we've maxed out the total number of retries
if (config.__retryCount >= config.retry) {
// Reject with the error
return Promise.reject(err)
}
// Increase the retry count
config.__retryCount += 1
// Create new promise to handle exponential backoff
let backoff = new Promise(function(resolve) {
setTimeout(function() {
resolve()
}, config.retryDelay || 1)
})
// Return the promise in which recalls axios to retry the request
return backoff.then(function() {
// 如果是开发环境使用的代理cors,这里的config.url需要再做下处理,不然url不正确.
return service(config)
})
}
)
1.5 合并
在请求错误或者超时时,弹出一个提示框提示用户,我们加入vant的toast插件,最后的完整代码:
/**集成axios,做包装, */
import axios from 'axios'
import { Toast } from 'vant'
// axios初始化
const service = axios.create({
baseURL:
process.env.NODE_ENV === 'production'
? 'http://你的正式环境服务器api地址/vueapi'
: 'http://jsonplaceholder.typicode.com/',
timeout: 5000
})
// Add a request interceptor 在发送请求之前做一些处理, 比如加入token之类的
service.interceptors.request.use(
function(config) {
/**全局loading */
if (config.showLoading) {
Toast.loading({
duration: 0,
forbidClick: true,
mask: true,
message: '处理中...'
})
}
// Do something before request is sent
// 这里加入token等信息。token可以存储在vuex或者本地存储中
return config
},
function(error) {
Toast.clear()
// Do something with request error
return Promise.reject(error)
}
)
// Add a response interceptor
//设置全局的请求次数,重试的时间间隔
service.defaults.retry = 0
service.defaults.retryDelay = 1000
/**
* 加入重新请求方法, 默认关闭
*/
service.interceptors.response.use(
function(response) {
Toast.clear()
// Do something with response data
// 请求返回处理,比如各类状态处理的, token过期等. 未登录等...
console.log('请求返回:', response)
return response
},
function axiosRetryInterceptor(err) {
Toast.clear()
let config = err.config
// If config does not exist or the retry option is not set, reject
if (!config || !config.retry) {
return Promise.reject(err)
}
// Set the variable for keeping track of the retry count
config.__retryCount = config.__retryCount || 0
// Check if we've maxed out the total number of retries
if (config.__retryCount >= config.retry) {
// Reject with the error
return Promise.reject(err)
}
// Increase the retry count
config.__retryCount += 1
// Create new promise to handle exponential backoff
let backoff = new Promise(function(resolve) {
setTimeout(function() {
resolve()
}, config.retryDelay || 1)
})
// Return the promise in which recalls axios to retry the request
return backoff.then(function() {
// 如果是开发环境使用的代理cors,这里的config.url需要再做下处理,不然url不正确.
return service(config)
})
}
)
/**
* 统一管理url,所有的请求经过一次封装.
* @param {*} uri 请求地址
* @param {*} params 请求参数
* @param {*} showLoading 是否展示loading
* @param {*} method 请求类型,post,get,put...等
*/
export default (uri, params, showLoading, method) => {
return service({
url: uri,
method: method === undefined ? 'get' : method,
data: params,
showLoading: showLoading ? true : false
})
.then(res => {
return res.data
})
.catch(err => {
if (err.response) {
console.log(err.response.data)
console.log(err.response.status)
console.log(err.response.headers)
if (err.response.status === 500) {
Toast.fail('服务器异常')
} else if (err.response.status === 404) {
Toast.fail('404!')
}
} else if (err.request) {
console.log(err.request)
Toast.fail('请求错误')
} else {
console.log('err:', err.message)
Toast.fail('未知错误')
}
})
}
1.6调用
包装完毕,咱们在users里面调用试试,在页面创建的时候调用一下:
//引入
import axios from '@/api/axios'
export default {
components: { NavBar },
created() {
//在初始化之后,调用:http://jsonplaceholder.typicode.com/users接口
axios('users', {}).then(res => {
console.log('res:', res)
})
},
methods: {
clickBtn() {
Toast.success('clicked!')
}
}
}
看看打印:
成功获取到了10条数据。
1.7 优化
虽然看起来我们对api的调用简化很多,但是为了方便管理,还要进一步划分,咱们依据不同的模块对接口拆分,users相关的存储在api/user
文件夹下,同样articles相关的存在api/article
文件夹下。
users.js
文件内容如下,导出一个方法users
,接收参数params
import axios from '@/api/axios'
/**
* 加载用户信息
*/
export const users = params => {
return axios('users', params, false, 'get')
}
articles.js
文件如下:
import axios from '@/api/axios'
/**
* 加载用户信息
*/
export const articles = params => {
return axios('posts', params, false, 'get')
}
我们将api都归类了,相应的调用api的地方也要修改。我们改下Users.vue的调用
<script>
import NavBar from '@/components/NavBar'
import { Toast } from 'vant'
import { users } from '@/api/user/users' // 引入users方法
export default {
components: { NavBar },
created() {
// 方法调用,传参为空
users({}).then(res => {
console.log('res:', res)
})
},
methods: {
clickBtn() {
Toast.success('clicked!')
}
}
}
</script>
Articles.vue的改造大家自己完成。
1.8 遗留问题
这里我说一个遗留问题,我们现在已经做到了数据请求,但是每次返回首页,然后再次进入users或者articles时,都会再次调用接口。数据量少,或者访问少的时候这样没多大的毛病,但是如果访问激增,或者数据量超级大,每次调用就会消耗服务端的资源,不怎么美好,如何优化?
2. van-list
好了,数据咱们都有了,现在在页面展示下。
List 组件通过loading和finished两个变量控制加载状态,当组件滚动到底部时,会触发load事件并将loading设置成true。此时可以发起异步操作并更新数据,数据更新完毕后,将loading设置成false即可。若数据已全部加载完毕,则直接将finished设置成true即可。
通过官网的描述我们可以了解,要想控制list的展示,需要4个参数
- loading控制
- finished控制
- load事件
- 要展示的数据users
要的资源已经创建,那么就按照流程进行编码了,首先,刚进入页面的时候,list组件开始调用onload方法,我们将loading设为true,finished为false。onload方法中开始异步加载users接口数据,返回成功之后将loading设为false,如果我们判定接口没有更多数据了,就设置finished为true。
为了测试有限的数据加载,我们加一个计数器,当users方法被调用2次之后,设置finished为true。之后将不加载数据。
Users.vue完整代码:
<template>
<!-- eslint-disable-->
<div>
<NavBar>
<div slot="right-btn">
<van-button size="small"
round
@click="clickBtn"
type="primary">提交</van-button>
</div>
</NavBar>
<div class="userlist">
<van-list v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad">
<van-cell v-for="user in users"
:key="user.id"
:title="user.name" />
</van-list>
</div>
</div>
<!-- eslint-enable -->
</template>
<script>
import NavBar from '@/components/NavBar'
import { Toast } from 'vant'
import { users } from '@/api/user/users'
import { setTimeout } from 'timers'
export default {
data() {
return {
loading: false,
finished: false,
users: [],
count: 0
}
},
components: { NavBar },
methods: {
onLoad() {
// 防止this指向错误,使用that
const that = this
// 模拟请求时间间隔,观察方法调用
setTimeout(function() {
users({}).then(res => {
that.users = Array.prototype.concat.apply(that.users, res)
that.loading = false
that.count += 1
if (that.count >= 2) {
that.finished = true
}
})
}, 3000)
},
clickBtn() {
Toast.success('clicked!')
}
}
}
</script>
<style scoped>
.userlist {
margin-top: 46px;
}
</style>
我在data设置了一个count,作为控制请求次数的控制器。然后在onload设置一个延迟调用模拟网络延迟。最后效果:
van-list中可以自定义样式,我这里为了方便就直接使用官网的样式了。
3. van-pull-refresh
嗯,我们上面实现了下拉加载旧数据,如果有新数据来了,怎么更新呢?一般就是需要做下拉刷新了。vant也实现了这个,我们再继续改造Users.vue
<template>
<!-- eslint-disable-->
<div>
<NavBar>
<div slot="right-btn">
<van-button size="small"
round
@click="clickBtn"
type="primary">提交</van-button>
</div>
</NavBar>
<div class="userlist">
<van-list v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad">
<van-pull-refresh v-model="isLoading"
@refresh="onRefresh">
<van-cell v-for="user in users"
:key="user.id"
:title="user.name" />
</van-pull-refresh>
</van-list>
</div>
</div>
<!-- eslint-enable -->
</template>
<script>
import NavBar from '@/components/NavBar'
import { Toast } from 'vant'
import { users } from '@/api/user/users'
import { setTimeout } from 'timers'
export default {
data() {
return {
isLoading: false,
loading: false,
finished: false,
users: [],
count: 0
}
},
components: { NavBar },
methods: {
onRefresh() {
const that = this
setTimeout(function() {
users({}).then(res => {
that.users = res
that.isLoading = false
that.count = 0
that.finished = false
})
}, 3000)
},
onLoad() {
// 防止this指向错误,使用that
const that = this
// 模拟请求时间间隔,观察方法调用
setTimeout(function() {
users({}).then(res => {
that.users = Array.prototype.concat.apply(that.users, res)
that.loading = false
that.count += 1
if (that.count >= 2) {
that.finished = true
}
})
}, 3000)
},
clickBtn() {
Toast.success('clicked!')
}
}
}
</script>
<style scoped>
.userlist {
margin-top: 46px;
}
</style>
效果:
以上。