大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: Vue.js 修改讲义

Vue本身并不提供Ajax的功能。

我们可以使用原生的JavaScript或JQuery来完成Ajax的操作。

但是Vue推荐使用axios,它是promise风格的(复习:回调地狱),飞哥觉得同学们也可以了解一下:


准备和引入

axios现阶段可以直接引用js文件:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

引入axios之后我们就能得到一个全局变量:axios(F12演示)


发起请求

和JQuery一样,首先提供一个基本/全能操作函数:axios(config),其中的config可以包括:

  • url:ajax请求的地址,(唯一)必须的
  • method:请求方式,字符串格式,如:"get"/"post"/"put"/"delete"……,默认get
  • params: 普通简单对象,通常用于get请求,代替url参数,比如:
    method: "get",
    params: {
        enrolled: 1,
        graduate: true
    }
  • data:通常是(JSON)对象格式,用于post/put/delete等请求(不可用于get),存储提交的数据
F12演示:options中的url(params转换)和body(data传输)
headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
},
  • FormData:application/x-www-form-urlencoded
  • PayLoad:application/json (默认
  • 文件图片等:multipart/form-data

以及:

  • withCredentials: false, 跨域请求时是否需要使用凭证// default
  • responseType: 'json', // default
  • responseEncoding: 'utf8', // default


上传文件

通过form文件表单拿到文件:(复习:事件和ref

<form id="yz-hello">
    <input type="file" name="icon" id="" v-on:change="upload" ref="icon">
</form>

我们可以:

  • 把所有form表单数据一次性的“form化”(复习:构建FormData
    var form = this.$refs.icon.parentElement,    //form表单元素
        formData = new window.FormData(form);   //注意构造函数参数,name
  • 也可以只处理文件表单元素:
    var icon = this.$refs.icon,
        formData = new window.FormData();    //注意window.不能省略
    formData.append('icon', icon.files[0])

@想一想@:为什么window.不能省略?

早期版本中还需要在config中添加HTTP Header:

headers: {
    'Content-Type': 'multipart/form-data'
},

利用后台WebApi演示:能收到上传文件

onUploadProgress

config中还可以配置onUploadProgress,指定文件上传中的事件:

onUploadProgress: function (progressEvent) {//@想一想@有没有坑?
    console.log('onUploadProgress...');//在文件开始上传时触发
},

@试一试@:能不能将信息显示在HTML元素上?

<small v-show="inProgress">上传中……</small>
data: {
    inProgress: false
},
onUploadProgress中:
this.inProgress = true;    //行不行?

注意:这里的this指代的是谁?!

axios推荐总是使用箭头函数:

onUploadProgress: (progressEvent) => {
    this.inProgress = true;
},

最后,可以利用progressEvent获取上传的进度:

//已经上传大小/总上传大小
this.percentage = progressEvent.loaded / progressEvent.total;

演示为了效果需要使用大文件,所以需要通过web.config的上传文件大小限制


处理响应

then()

axios.request()返回的是一个promise对象,所以可以then(handler);handler作为回调函数,带一个参数(通常命名为response),包含的信息按有用性依次是:

  1. data: {},由服务器提供的响应
  2. status: 200,响应的 HTTP 状态码
  3. headers: {},服务器响应头,所有的 header 名称都是小写,可以使用方括号语法访问,例如: `response.headers['content-type']`,
  4. statusText: 'OK',响应的 HTTP 文本信息
  5. config: {},请求的配置信息
  6. request: {},生成此响应的请求

后台WebApi演示:显示上传的文件

需要一个img标签:

<img v-if="shown" :src="imgSrc" alt="图片" />
imgSrc是vue data,在then()的回调函数里赋值:
this.imgSrc = response.data;
注意这个shown,不需要设置在vue data里面,可以使用计算属性复习
computed: {
    shown: () => {
        return Boolean(this.imgSrc);
    }
},

catch()

如果Ajax请求出现异常,就会调用catch()中的回调函数。该函数带一个参数(通常命名error),根据错误发生的阶段,又包含以下信息:

  1. response:请求成功发出且服务器也予以响应,但状态码大于2xx,其中又包含
    1. data:响应内容
    2. status:响应状态
    3. headers:响应头
    等信息。
  2. request:请求已经成功发起,但没有收到响应
  3. message:发送请求时出了点问题
所以我们推荐的写法是:
if (error.response) {
    console.log(error.response.data);
    console.log(error.response.status);
    console.log(error.response.headers);
} else if (error.request) {
    console.log(error.request);
} else {
    console.log('Error', error.message);
}

finally

因为ES标准和浏览器支持的原因,axios官方文档里面并没有提到finally()。

但现在主流的浏览器中已经能够使用finally了(没有回调参数参数);注意这不是因为axios,而是因为Promise。

timeout

还可以在config中指定:timeout,

timeout: 500,

单位毫秒,超时后就直接走catch的error.request。

其实,服务器端一般也有timeout,但axios中设置timeout给前端开发人员更多控制,在某些特殊情况下应该有用,比如:按钮被点击后禁用,过一段时间就解禁。

对比演示:访问一个

  • 无法连接的网址:http://twitter.com
  • 可以连通但耗时过长由前端主动终止

PS:axios中还可以配置cancelToken取消请求,用得很少,略


连缀

实际开发中我们经常需要碰到这样的场景:

  1. 首先需要发起一个Ajax请求获取数据,比如某个学生的年龄,
  2. 然后再根据学生的年龄发起另外一个Ajax请求,比如该年龄能享受到的学费折扣,
  3. 再然后……

如果是JQuery,我们就会形成回调地狱;但使用axios,我们能够promise连缀。

后一个then()一定是在前一个then()执行完成之后才执行。

所以可以很放心的使用前一个then里面修改过的vue data:

}).then((response) => {
    this.imgSrc = response.data;
}).then((response) => {
    console.log(this.imgSrc);
})

但是,在某些特殊场景下,没有/不需要vue data做中介,你还能拿到前面一个then()里的值么?

}).then((response) => {
    //假设没有this.imgSrc作为中介?
    return response.data
}).then((data) => {
    console.log(data);
})

等等,我第2个then里面是要发起Ajax请求的,而且还有第3个then呢?

}).then((response) => {
    axios.request({ url: "", params: { path: this.imgSrc } })
        .then(/*这不又地狱回调了么?*/)
})
axios中可以在then()后面继续接then()。但是,注意:前面一个then里面要加一个return
}).then((response) => {
    return axios.request({
        url: "/api/Student/2"}
    });
}).then((response) => { //response代表上一次请求的结果
    console.log(response.data);
})


简写方式

和JQuery一样,axios也根据请求的method不同,提供一些简写方法:

  • request(url, [config]):
    axios.request("/api/Student", {
        method: "post"    //指定method,默认是get
    })
    
  • get(url, [config]):
    axios.get("/api/Student", {    //确定是get请求
        params: {
            name: '大飞哥'
        }
    })
  • post(url, [config]):
    axios.post("/api/Student/2", {    //确定是post请求
        data: {
            name: '大飞哥'
        }
    })

类似的还有put()、delete()等……(略)


all()

还可以使用all()一次性发出多个请求:

axios.all([//注意方括号
    axios.get('/api/Student'),
    axios.get('/api/Student/1'),
    axios.get('/api/Student/2')
]).then(

注意:这里的all()和then()...then()是不一样的:

  • 多个axios之间没有执行的先后顺序
  • 但then()开始执行时,多个axios一定已经执行完毕


全局设置

在request中作为参数的config,只能影响本次提交。

如果要影响“所有”的axios提交,需要进行全局设置(又被称之为“默认配置”)。

axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.baseURL = 'https://17bang.ren';//可理解为url前缀
注意不要将其设置到:axios.defaults.post.headers里面(但我也不知道这玩意儿究竟干啥用的,详见源代码演示)
config.headers = utils.merge(
  config.headers.common || {},
  config.headers[config.method] || {},
  config.headers
);

utils.forEach(
  ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
  function cleanHeaderConfig(method) {
    delete config.headers[method];
  }
);
有时候我们会想让设置只影响“一部分”的请求,这时候我们可以
  • 新建(create)一个axios实例,在新建时指定config
    let ax = axios.create({
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    });
  • 用这个新建实例发起请求
    ax.post("/api/Student", {    //注意不是axios

这样,凡是由ax发起的请求都会默认使用其创建时的配置。

你也一样可以在已生成的ax上设置defaults:

ax.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';

网上有一种说法:

配置会以一个优先顺序进行合并

注意不要误解,准确来说,应该是:

  1. 单个的请求方法中如果没有配置,就会使用axios的默认配置
  2. 默认配置就到axios.defaults中找,而我们可以用赋值的方式修改axios.defaults的值


拦截器

除了全局配置,有时候我们还希望对所有/部分的请求做一些额外的(request发起/得到respons前)处理。这就需要使用拦截器(interceptor)

  • 在发送请求之前
    axios.interceptors.request.use(function (config) {
        //.... 一般就是配置config,比如添加/修改baseURL、header键值对等
        return config;
    }, function (error) { // 当请求出现错误
        //....
        return Promise.reject(error);
    });
    
  • 在处理响应数据之前
    axios.interceptors.response.use(function (response) {
        //....
        return response;
    }, function (error) {// 当响应错误
        //.... 一般就是:1. 提示用户;2.记录并传回错误信息
        return Promise.reject(error);
    });

和vue的配合

通常都用于设置vue data。可以是:


  • 在DOM元素渲染的时候:
    mounted: function () {
        axios.get("/Student/23").then(response => {
            this.sname = response.data.sname;
        })
    }
  • 响应事件处理程序

其实因为vue的响应式渲染,不用担心then()的异步执行。



说在最后

比如content-type、response type/encoding究竟选什么?

一句话:需要前后端协商/协调决定。

前后端分离能够顺利有效进行的一个核心要件:良好的API。


作业

纯前端通过mock.js或模拟静态HTML片段页面(全栈需要完整实现),通过axios完成以下功能:

  1. 自动加载:
    1. 最新消息
    2. 文章列表
    3. 右侧widgets:
      1. 关键字
      2. 排行榜
      3. 广告
    关于分页,前端给后端的要求:
    • 可以是:给第2页的文章(每页10篇),告诉我一共有多少页
    • 也可以是:给第11-20篇文章,告诉我一共多少篇文章
    但前端要能将用户点击第几页、上/下一页的信息传递给后端。
  2. 根据当前用户(纯前端通过vue data模拟,全栈通过cookie确定)是否有新通知决定铃铛是否闪烁,注意:
    1. 需要用定时器每隔一段时间就检查一次
    2. 如果铃铛已经闪烁,就不用检查;
    3. 除非用户标记已读/删除了通知
  3. 注册页面,完成检查:
    1. 用户名已使用
    2. 邀请人不存在,或邀请码错误的检测
  4. 文章发布页面,实现添加新系列的功能。注意:已有系列数据从后台获得

学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 Vue.js 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码