vue手撸移动端后台(7)axios网络请求和模拟数据

本文介绍如何在Vue项目中手写移动端后台,使用axios进行网络请求,并利用模拟数据接口http://jsonplaceholder.typicode.com/进行数据操作。通过封装axios,设置环境切换、token处理和错误处理,实现下拉刷新(van-pull-refresh)和滑动加载更多(van-list)功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们的前端页面大致上整理完毕,下面进入对数据的操作了,不然不知道怎么结合数据岂不是瞎玩么。
有一些么有后台接口经验的前端同学也不要慌,咱们搞个简单的,也不要自己提供接口,网上都有写好的。

http://jsonplaceholder.typicode.com/
这个网站,提供了公共的一些可以访问的接口

我们就用这个网站的接口了,直接浏览器访问就有返回数据

  1. 获取10个用户:http://jsonplaceholder.typicode.com/users
  2. 获取单个用户:http://jsonplaceholder.typicode.com/users/1
  3. 获取100个post:http://jsonplaceholder.typicode.com/posts
  4. 获取单个post:http://jsonplaceholder.typicode.com/posts/1

我们改造一下users的列表样式,让他好看点。然后实现一下下拉刷新和滑动到底部加载更多数据。
这里用到的2个控件:van-listvan-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>

效果:
在这里插入图片描述

以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值