Vue基础-Day03
到此为止,Vue基础内容做前端基本交互已经基本够用,但是数据使用的依然是假数据,为了把数据变成真实的场景,我们需要与后端进行交互(接口调用) 与接口调用密切相关的知识模块:异步编程
- 异步的结果是否可以使用返回值获取呢?不可以(返回结果的时机不确定)
- 那么必须使用回调函数的方式获取异步的结果
- 如果要保证异步任务的顺序,需要进行回调函数的嵌套
- 但是嵌套过多就会出现回调地狱问题(代码可读性较差)
- 所以就诞生了新的技术解决上述问题:Promise
- 但是Promise也不是最好的方案,所以就诞生了Async函数(终极解决方案)
异步编程-Promise
Promise是一种技术,用于解决回调地狱问题(仅仅是改进代码的风格)
Promise基本用法
目标:熟悉基于Promise发送请求的基本流程
// 基于Promise发送请求const p = new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})// 通过p.then方法获取异步的结果p.then(function (ret) {// 这里可以得到正常的结果console.log(ret)}).catch(function (err) {// 这里可以得到异常的结果console.log(err)})
总结:
- 熟悉基于Promise处理异步任务的基本流程
- 正常的结果传递给resolve;异常的结果传递给reject
- 通过p.then获取正常的结果,p.catch获取异常的结果
解决回调地狱问题
目标:能够通过Promise方式解决回调地狱的问题
function queryData1() {// 基于Promise发送请求return new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data1')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})}function queryData2() {// 基于Promise发送请求return new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data2')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})}function queryData3() {// 基于Promise发送请求return new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data3')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})}// 如下代码执行的流程是串行的queryData1().then(function (ret) {console.log(ret)return queryData2()}).then(function (ret) {console.log(ret)return queryData3()}).then(function (ret) {console.log(ret)})

总结:
- 基于Promise处理多个异步任务代码是水平的,可读性较高
- 基于回调嵌套的代码的风格可读性较差
- Promise的代码风格是回调嵌套的风格的改进(功能不变)
- 语法糖:A是B的语法糖(A是对B的一种改进)
- Promise是回调嵌套的一种语法糖(提升了程序员的编程体验)
- 关于Promise的异常处理
// 如下代码执行的流程是串行的queryData1().then(function (ret) {console.log(ret)// 如果then中出现了异常情况,那么后续的then就不再执行了const arr = nullarr.push(123)return queryData2()}).then(function (ret) {console.log(ret)return queryData3()}).then(function (ret) {console.log(ret)}).catch(function (err) {// 这里用于处理异常信息console.log(err)})
注意:
- 如果then中出现了异常情况,那么后续的then就不再执行了
- 一般会再then的最后添加一个catch,统一处理异常
then的回调返回值问题
目标:属性then的回调函数的返回值的不同情况
// 如下代码执行的流程是串行的queryData1().then(function (ret) {console.log(ret)// 如果then中出现了异常情况,那么后续的then就不再执行了// const arr = null// arr.push(123)return queryData2()}).then(function (ret) {console.log(ret)// 1、这里return的值是谁?Promise的实例对象// 如果这里return的是Promise的实例对象,那么// 下一个then会得到这个Promise的异步结果// 其实就是这个Promise对象调用了下一个thenreturn queryData3()}).then(function (ret) {console.log(ret)// 2、如果这里没有返回值,那么会默认生成一个Promise对象并返回// return new Promise(function (resolve) {// resolve()// })}).then(function (ret) {console.log(ret)console.log('---------------------')// 3、如果这里返回的是普通数据,那么下一个then可以直接得到该数据// 如果返回的是普通数据,会自动包装为Promise实例对象return 'hello'// return new Promise(function (resolve) {// resolve('hello')// })}).then(function (ret) {console.log(ret)}).catch(function (err) {// 这里用于处理异常信息console.log(err)})
总结:
- then回调函数返回Promise对象,下一个then得到这个Promise任务的结果
- then回调函数返回普通数据,下一个then可以直接得到这个数据(数据被默认包装成了Promise对象)
- then回调函数没有返回,但是可以继续then链式操作(默认返回了一个Promise实例对象,但是没有值)
异步编程-Async函数
Async函数基本用法
目标:熟悉Async函数基本使用
// Asyn函数基本使用function queryData() {// 基于Promise发送请求return new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})}// queryData().then(function (ret) {// console.log(ret)// })// 如下就是定义了一个async函数,可以处理异步任务async function getInfo() {// await关键自必须在async函数中使用// await之后一般跟Promise实例对象// const ret = await queryData()// console.log(ret)// 通过Promise的then方式获取结果和await获取结果等效queryData().then(function (ret) {console.log(ret)})}getInfo()
总结:
- 定义async函数仅仅需要在函数声明的左侧添加async关键字
- await关键字必须出现在async函数内部
- await关键字之后一般会跟Promise实例对象
- await代码行之后的代码是阻塞的
Async处理多个异步任务
目标:基于Async函数处理回调地狱问题
// 要求:获取的顺序 jerry -> spike -> tomasync function getResult() {const ret2 = await queryData2()const ret3 = await queryData3()const ret1 = await queryData1()console.log(ret2)console.log(ret3)console.log(ret1)}getResult()

总结:
- 基于Async函数处理回调地狱可以按照同步的风格写代码,体验很好
- 多个await运行模式是串行的
Async函数和Promise的关系
需求:熟悉Async函数和Promise的关联关系
// Promise和Async函数的关系// 1、await之后一把跟Promise实例对象,await左侧接收的值就是Promise异步任务的结果// 2、async函数的返回值是Promise对象,而不是具体数据function queryData() {// 基于Promise发送请求return new Promise(function (resolve, reject) {// 这里用于处理异步的任务const xhr = new XMLHttpRequest()xhr.open('get', 'http://localhost:3000/data')xhr.send(null)xhr.onreadystatechange = function () {if (xhr.readyState === 4) {if (xhr.status === 200) {// 获取正常的数据let ret = xhr.responseTextlet obj = JSON.parse(ret)// 正常的结果传递给resolve即可resolve(obj)} else {// 返回的数据是异常的// 异常的结果需要传递给rejectreject('服务器返回的数据错误')}}}})}async function getInfo() {const ret = await queryData()// console.log(ret)return ret// 这里返回的数据实际上会默认包装为Promise实例对象// return new Promise(function (resolve) {// resolve(ret)// })}// async函数的返回值是Promise对象,而不是具体数据const t = getInfo()// console.log(t)t.then(function (ret) {console.log(ret)})
总结:
- await之后一把跟Promise实例对象,await左侧接收的值就是Promise异步任务的结果
- async函数的返回值是Promise对象,而不是具体数据
axios调用接口
目标:熟悉async函数中使用axios调用接口的写法
<body><script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script><script>// axios调用接口// axios.get('http://localhost:3000/data').then(function (ret) {// console.log(ret.data)// })axios.defaults.baseURL = 'http://localhost:3000/'async function getResult() {const ret = await axios.get('data')console.log(ret.data)}getResult()// 如下的伪代码new Vue({el: '#app',data: {list: []},methods: {// loadBookList: async function () {// const ret = await axios.get('data')// this.list = ret.data// },async loadBookList() {const ret = await axios.get('data')this.list = ret.data}}})</script></body>
总结:
- axios库底层封装了Promise,所有他的API一般都支持Promise方式
- 基于async函数处理axios的接口调用会变得异常简单
前后端交互
json-server基本使用
目的:模拟后台接口。
问题:前端需要依赖后台接口才能获取数据,但是我们前端开发的时候,后台一定写好了接口吗?
模拟接口:使用json-server这个基于nodejs的命令行工具,模拟后台接口,让前端可以继续进行开发。
- 安装 json-server
npm i json-server -g --registry=https://registry.npm.taobao.org
- 准备db.json文件
{ "books": [ {"id":1,"bookname":"西游记","date":"2010-10-10 10:10:10"} ]}
- 进入db.json文件所在目录,启动server服务
json-server db.json
- 如果json-server服务启动了,我们通过 http://localhost:3000/books 访问数组数据。
总结:json-server作用(基于json文件的数据,直接生成后端接口,方便测试)
- 全局安装json-server包
- 创建json数据文件
- 通过json-server命令启动数据文件的监听
- 通过url地址测试接口即可
通过postman测试接口
目的:了解接口规则。
我们使用json-server启动的接口符合Restful接口定义的规则。
- url路径中一般使用名词的复数
- 请求的参数使用 /123 这种风格传递,而不使用 ?id=123这种风格
- Restful 风格:/books/123
- 传统风格:/books?id=123
- 严格区分请求方式(必须按照文档的方式采用请求方式) | 路径 | 请求方式 | 具体对应操作 | | —- | —- | —- | | /books | GET | 获取图书数组数据 | | /books/1 | GET | 获取指定id的图书数据 | | /books | POST | 添加图书(请求体传参) | | /books/1 | DELETE | 删除图书 | | /books/1 | PUT | 修改图书(请求体传参) 完整修改 | | /books/1 | PATCH | 修改图书(请求体传参) 局部修改 |
总结:
- 请求地址相同,但是请求方式不同,那么属于不同的地址
- 动态参数文档中一般这样写 /books/:id
const id = 123$.ajax({ // 实际发出的请求地址是 /books/123 url: '/books/' + id})
注意:PUT和PATCH请求方式的区别
图书案例案例-接口版
图书列表功能
目标:实现图书列表渲染功能(调用接口实现)
- 页面打开时自动触发接口调用 created
- 接口获取数据成功后,更新data中的列表数据
// 配置axios的基准路径axios.defaults.baseURL = 'http://localhost:3000/'methods: {async loadBookList() {// 调用后端接口获取图书列表数据const ret = await axios.get('books')// 从后端获取数据对data中的属性进行初始化this.books = ret.data},// loadBookList() {// // 调用后端接口获取图书列表数据// // const that = this// axios.get('http://localhost:3000/books')// .then((ret) => {// this.books = ret.data// })// },},created () {this.loadList()}
总结:
- created方法是vue提供的,页面加载时自动触发
- 接口调用采用Promise和Async函数的用法都要掌握
删除图书
目标:实现图书删除功能
async handleDlete (id) {// 删除图书:根据id调用接口删除图书,删除成功后刷新列表const res = await axios.delete('books/' + id)if (res.status === 200) {// 删除成功,刷新列表this.loadBookList()}},
总结:绑定事件;获取要删除的图书的id;调用接口删除;判断返回的状态并刷新列表
添加图书
目标:实现添加图书功能
- 绑定按钮事件
- 获取表单数据
- 调用接口添加图书
- 根据返回的状态刷新列表
- 清空表单
async handleSubmit () {// 把表单中输入的数据添加到数组中if (!this.bookname) {alert('请输入图书名称')return}// 调用接口添加图书const ret = await axios.post('books', book)if (ret.status === 201 && ret.data.id) {// 添加成功,刷新列表,清空表单this.loadBookList()this.bookname = ''this.author = ''} else {alert('添加图书失败')}}}
总结:绑定事件;获取表单数据;表单验证;调用接口添加图书;返回状态判断;刷新列表清空表单。
搜索图书
目标:掌握vue侦听器功能
侦听器应用场景:在需要监听某项数据的变化然后去,做异步操作或开销较大操作(逻辑非常复杂)。
补充:凡是以后需要监听数据的变化而去做一些事情,就可以使用侦听器。
- 基本用法
// 侦听器基本用法const vm = new Vue({el: '#app',data: {msg: 'Jim Green',firstname: 'Jim',lastname: 'Green'},watch: {// 侦听器(监听msg属性的变化)// msg(newMsg, oldMsg) {// // newMsg表示msg被修改后的值// // oldMsg表示msg被修改前的值// console.log(newMsg, oldMsg)// }firstname(v) {this.msg = v + ' ' + this.lastname},lastname(v) {this.msg = this.firstname + ' ' + v}}})
总结:
- 侦听器本质是函数,但是函数名称需要和侦听的属性名称标尺一致
- 侦听器函数的形参有两个,分别是:修改后的值和修改前的值
- 基于侦听器实现搜索功能
<input v-model='keyword' type="text" class="form-control" style="width:200px;display: inline-block;" placeholder="请输入搜索关键字">
data: {// 品牌列表list: [],// 品牌名称bookname: '',// 搜索关键字keyword: ''},
watch: {async keyword (v) {// 发送请求:根据关键字查询后端接口数据const ret = await axios.get('books', {params: {// username后面添加_like表示模糊匹配// _like其实是json-server规定的bookname_like: v}})// 把匹配的结果覆盖默认列表数据this.books = ret.data}}
总结:
- 基于侦听器方式监听输入的关键字的变化
- 调用接口是通过参数的_like实现模糊匹配,规则由json-server规定
