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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
// 通过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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
}
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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
}
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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
}
// 如下代码执行的流程是串行的
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 = null
arr.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对象调用了下一个then
return 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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
}
// 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 -> tom
async 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.responseText
let obj = JSON.parse(ret)
// 正常的结果传递给resolve即可
resolve(obj)
} else {
// 返回的数据是异常的
// 异常的结果需要传递给reject
reject('服务器返回的数据错误')
}
}
}
})
}
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规定