Vue基础-Day03

到此为止,Vue基础内容做前端基本交互已经基本够用,但是数据使用的依然是假数据,为了把数据变成真实的场景,我们需要与后端进行交互(接口调用) 与接口调用密切相关的知识模块:异步编程

  • 异步的结果是否可以使用返回值获取呢?不可以(返回结果的时机不确定)
  • 那么必须使用回调函数的方式获取异步的结果
  • 如果要保证异步任务的顺序,需要进行回调函数的嵌套
  • 但是嵌套过多就会出现回调地狱问题(代码可读性较差)
  • 所以就诞生了新的技术解决上述问题:Promise
  • 但是Promise也不是最好的方案,所以就诞生了Async函数(终极解决方案)

异步编程-Promise

Promise是一种技术,用于解决回调地狱问题(仅仅是改进代码的风格)

Promise基本用法

目标:熟悉基于Promise发送请求的基本流程

  1. // 基于Promise发送请求
  2. const p = new Promise(function (resolve, reject) {
  3. // 这里用于处理异步的任务
  4. const xhr = new XMLHttpRequest()
  5. xhr.open('get', 'http://localhost:3000/data')
  6. xhr.send(null)
  7. xhr.onreadystatechange = function () {
  8. if (xhr.readyState === 4) {
  9. if (xhr.status === 200) {
  10. // 获取正常的数据
  11. let ret = xhr.responseText
  12. let obj = JSON.parse(ret)
  13. // 正常的结果传递给resolve即可
  14. resolve(obj)
  15. } else {
  16. // 返回的数据是异常的
  17. // 异常的结果需要传递给reject
  18. reject('服务器返回的数据错误')
  19. }
  20. }
  21. }
  22. })
  23. // 通过p.then方法获取异步的结果
  24. p.then(function (ret) {
  25. // 这里可以得到正常的结果
  26. console.log(ret)
  27. }).catch(function (err) {
  28. // 这里可以得到异常的结果
  29. console.log(err)
  30. })

总结:

  1. 熟悉基于Promise处理异步任务的基本流程
  2. 正常的结果传递给resolve;异常的结果传递给reject
  3. 通过p.then获取正常的结果,p.catch获取异常的结果

解决回调地狱问题

目标:能够通过Promise方式解决回调地狱的问题

  1. function queryData1() {
  2. // 基于Promise发送请求
  3. return new Promise(function (resolve, reject) {
  4. // 这里用于处理异步的任务
  5. const xhr = new XMLHttpRequest()
  6. xhr.open('get', 'http://localhost:3000/data1')
  7. xhr.send(null)
  8. xhr.onreadystatechange = function () {
  9. if (xhr.readyState === 4) {
  10. if (xhr.status === 200) {
  11. // 获取正常的数据
  12. let ret = xhr.responseText
  13. let obj = JSON.parse(ret)
  14. // 正常的结果传递给resolve即可
  15. resolve(obj)
  16. } else {
  17. // 返回的数据是异常的
  18. // 异常的结果需要传递给reject
  19. reject('服务器返回的数据错误')
  20. }
  21. }
  22. }
  23. })
  24. }
  25. function queryData2() {
  26. // 基于Promise发送请求
  27. return new Promise(function (resolve, reject) {
  28. // 这里用于处理异步的任务
  29. const xhr = new XMLHttpRequest()
  30. xhr.open('get', 'http://localhost:3000/data2')
  31. xhr.send(null)
  32. xhr.onreadystatechange = function () {
  33. if (xhr.readyState === 4) {
  34. if (xhr.status === 200) {
  35. // 获取正常的数据
  36. let ret = xhr.responseText
  37. let obj = JSON.parse(ret)
  38. // 正常的结果传递给resolve即可
  39. resolve(obj)
  40. } else {
  41. // 返回的数据是异常的
  42. // 异常的结果需要传递给reject
  43. reject('服务器返回的数据错误')
  44. }
  45. }
  46. }
  47. })
  48. }
  49. function queryData3() {
  50. // 基于Promise发送请求
  51. return new Promise(function (resolve, reject) {
  52. // 这里用于处理异步的任务
  53. const xhr = new XMLHttpRequest()
  54. xhr.open('get', 'http://localhost:3000/data3')
  55. xhr.send(null)
  56. xhr.onreadystatechange = function () {
  57. if (xhr.readyState === 4) {
  58. if (xhr.status === 200) {
  59. // 获取正常的数据
  60. let ret = xhr.responseText
  61. let obj = JSON.parse(ret)
  62. // 正常的结果传递给resolve即可
  63. resolve(obj)
  64. } else {
  65. // 返回的数据是异常的
  66. // 异常的结果需要传递给reject
  67. reject('服务器返回的数据错误')
  68. }
  69. }
  70. }
  71. })
  72. }
  73. // 如下代码执行的流程是串行的
  74. queryData1()
  75. .then(function (ret) {
  76. console.log(ret)
  77. return queryData2()
  78. })
  79. .then(function (ret) {
  80. console.log(ret)
  81. return queryData3()
  82. })
  83. .then(function (ret) {
  84. console.log(ret)
  85. })

image.png

总结:

  1. 基于Promise处理多个异步任务代码是水平的,可读性较高
  2. 基于回调嵌套的代码的风格可读性较差
  3. Promise的代码风格是回调嵌套的风格的改进(功能不变)
  • 语法糖:A是B的语法糖(A是对B的一种改进)
  • Promise是回调嵌套的一种语法糖(提升了程序员的编程体验)
  • 关于Promise的异常处理
  1. // 如下代码执行的流程是串行的
  2. queryData1()
  3. .then(function (ret) {
  4. console.log(ret)
  5. // 如果then中出现了异常情况,那么后续的then就不再执行了
  6. const arr = null
  7. arr.push(123)
  8. return queryData2()
  9. })
  10. .then(function (ret) {
  11. console.log(ret)
  12. return queryData3()
  13. })
  14. .then(function (ret) {
  15. console.log(ret)
  16. })
  17. .catch(function (err) {
  18. // 这里用于处理异常信息
  19. console.log(err)
  20. })

注意:

  1. 如果then中出现了异常情况,那么后续的then就不再执行了
  2. 一般会再then的最后添加一个catch,统一处理异常

then的回调返回值问题

目标:属性then的回调函数的返回值的不同情况

  1. // 如下代码执行的流程是串行的
  2. queryData1()
  3. .then(function (ret) {
  4. console.log(ret)
  5. // 如果then中出现了异常情况,那么后续的then就不再执行了
  6. // const arr = null
  7. // arr.push(123)
  8. return queryData2()
  9. })
  10. .then(function (ret) {
  11. console.log(ret)
  12. // 1、这里return的值是谁?Promise的实例对象
  13. // 如果这里return的是Promise的实例对象,那么
  14. // 下一个then会得到这个Promise的异步结果
  15. // 其实就是这个Promise对象调用了下一个then
  16. return queryData3()
  17. })
  18. .then(function (ret) {
  19. console.log(ret)
  20. // 2、如果这里没有返回值,那么会默认生成一个Promise对象并返回
  21. // return new Promise(function (resolve) {
  22. // resolve()
  23. // })
  24. })
  25. .then(function (ret) {
  26. console.log(ret)
  27. console.log('---------------------')
  28. // 3、如果这里返回的是普通数据,那么下一个then可以直接得到该数据
  29. // 如果返回的是普通数据,会自动包装为Promise实例对象
  30. return 'hello'
  31. // return new Promise(function (resolve) {
  32. // resolve('hello')
  33. // })
  34. })
  35. .then(function (ret) {
  36. console.log(ret)
  37. })
  38. .catch(function (err) {
  39. // 这里用于处理异常信息
  40. console.log(err)
  41. })

总结:

  1. then回调函数返回Promise对象,下一个then得到这个Promise任务的结果
  2. then回调函数返回普通数据,下一个then可以直接得到这个数据(数据被默认包装成了Promise对象)
  3. then回调函数没有返回,但是可以继续then链式操作(默认返回了一个Promise实例对象,但是没有值)

异步编程-Async函数

Async函数基本用法

目标:熟悉Async函数基本使用

  1. // Asyn函数基本使用
  2. function queryData() {
  3. // 基于Promise发送请求
  4. return new Promise(function (resolve, reject) {
  5. // 这里用于处理异步的任务
  6. const xhr = new XMLHttpRequest()
  7. xhr.open('get', 'http://localhost:3000/data')
  8. xhr.send(null)
  9. xhr.onreadystatechange = function () {
  10. if (xhr.readyState === 4) {
  11. if (xhr.status === 200) {
  12. // 获取正常的数据
  13. let ret = xhr.responseText
  14. let obj = JSON.parse(ret)
  15. // 正常的结果传递给resolve即可
  16. resolve(obj)
  17. } else {
  18. // 返回的数据是异常的
  19. // 异常的结果需要传递给reject
  20. reject('服务器返回的数据错误')
  21. }
  22. }
  23. }
  24. })
  25. }
  26. // queryData().then(function (ret) {
  27. // console.log(ret)
  28. // })
  29. // 如下就是定义了一个async函数,可以处理异步任务
  30. async function getInfo() {
  31. // await关键自必须在async函数中使用
  32. // await之后一般跟Promise实例对象
  33. // const ret = await queryData()
  34. // console.log(ret)
  35. // 通过Promise的then方式获取结果和await获取结果等效
  36. queryData().then(function (ret) {
  37. console.log(ret)
  38. })
  39. }
  40. getInfo()

总结:

  1. 定义async函数仅仅需要在函数声明的左侧添加async关键字
  2. await关键字必须出现在async函数内部
  3. await关键字之后一般会跟Promise实例对象
  4. await代码行之后的代码是阻塞的

Async处理多个异步任务

目标:基于Async函数处理回调地狱问题

  1. // 要求:获取的顺序 jerry -> spike -> tom
  2. async function getResult() {
  3. const ret2 = await queryData2()
  4. const ret3 = await queryData3()
  5. const ret1 = await queryData1()
  6. console.log(ret2)
  7. console.log(ret3)
  8. console.log(ret1)
  9. }
  10. getResult()

image.png

总结:

  1. 基于Async函数处理回调地狱可以按照同步的风格写代码,体验很好
  2. 多个await运行模式是串行的

Async函数和Promise的关系

需求:熟悉Async函数和Promise的关联关系

  1. // Promise和Async函数的关系
  2. // 1、await之后一把跟Promise实例对象,await左侧接收的值就是Promise异步任务的结果
  3. // 2、async函数的返回值是Promise对象,而不是具体数据
  4. function queryData() {
  5. // 基于Promise发送请求
  6. return new Promise(function (resolve, reject) {
  7. // 这里用于处理异步的任务
  8. const xhr = new XMLHttpRequest()
  9. xhr.open('get', 'http://localhost:3000/data')
  10. xhr.send(null)
  11. xhr.onreadystatechange = function () {
  12. if (xhr.readyState === 4) {
  13. if (xhr.status === 200) {
  14. // 获取正常的数据
  15. let ret = xhr.responseText
  16. let obj = JSON.parse(ret)
  17. // 正常的结果传递给resolve即可
  18. resolve(obj)
  19. } else {
  20. // 返回的数据是异常的
  21. // 异常的结果需要传递给reject
  22. reject('服务器返回的数据错误')
  23. }
  24. }
  25. }
  26. })
  27. }
  28. async function getInfo() {
  29. const ret = await queryData()
  30. // console.log(ret)
  31. return ret
  32. // 这里返回的数据实际上会默认包装为Promise实例对象
  33. // return new Promise(function (resolve) {
  34. // resolve(ret)
  35. // })
  36. }
  37. // async函数的返回值是Promise对象,而不是具体数据
  38. const t = getInfo()
  39. // console.log(t)
  40. t.then(function (ret) {
  41. console.log(ret)
  42. })

总结:

  1. await之后一把跟Promise实例对象,await左侧接收的值就是Promise异步任务的结果
  2. async函数的返回值是Promise对象,而不是具体数据

axios调用接口

目标:熟悉async函数中使用axios调用接口的写法

  1. <body>
  2. <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
  3. <script>
  4. // axios调用接口
  5. // axios.get('http://localhost:3000/data').then(function (ret) {
  6. // console.log(ret.data)
  7. // })
  8. axios.defaults.baseURL = 'http://localhost:3000/'
  9. async function getResult() {
  10. const ret = await axios.get('data')
  11. console.log(ret.data)
  12. }
  13. getResult()
  14. // 如下的伪代码
  15. new Vue({
  16. el: '#app',
  17. data: {
  18. list: []
  19. },
  20. methods: {
  21. // loadBookList: async function () {
  22. // const ret = await axios.get('data')
  23. // this.list = ret.data
  24. // },
  25. async loadBookList() {
  26. const ret = await axios.get('data')
  27. this.list = ret.data
  28. }
  29. }
  30. })
  31. </script>
  32. </body>

总结:

  1. axios库底层封装了Promise,所有他的API一般都支持Promise方式
  2. 基于async函数处理axios的接口调用会变得异常简单

前后端交互

json-server基本使用

目的:模拟后台接口。

问题:前端需要依赖后台接口才能获取数据,但是我们前端开发的时候,后台一定写好了接口吗?

模拟接口:使用json-server这个基于nodejs的命令行工具,模拟后台接口,让前端可以继续进行开发。

  1. npm i json-server -g --registry=https://registry.npm.taobao.org
  • 准备db.json文件
  1. { "books": [ {"id":1,"bookname":"西游记","date":"2010-10-10 10:10:10"} ]}
  • 进入db.json文件所在目录,启动server服务
  1. json-server db.json

总结:json-server作用(基于json文件的数据,直接生成后端接口,方便测试)

  1. 全局安装json-server包
  2. 创建json数据文件
  3. 通过json-server命令启动数据文件的监听
  4. 通过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 | 修改图书(请求体传参) 局部修改 |

总结:

  1. 请求地址相同,但是请求方式不同,那么属于不同的地址
  2. 动态参数文档中一般这样写 /books/:id
  1. const id = 123$.ajax({ // 实际发出的请求地址是 /books/123 url: '/books/' + id})

注意:PUT和PATCH请求方式的区别

图书案例案例-接口版

图书列表功能

目标:实现图书列表渲染功能(调用接口实现)

  • 页面打开时自动触发接口调用 created
  • 接口获取数据成功后,更新data中的列表数据
  1. // 配置axios的基准路径
  2. axios.defaults.baseURL = 'http://localhost:3000/'
  3. methods: {
  4. async loadBookList() {
  5. // 调用后端接口获取图书列表数据
  6. const ret = await axios.get('books')
  7. // 从后端获取数据对data中的属性进行初始化
  8. this.books = ret.data
  9. },
  10. // loadBookList() {
  11. // // 调用后端接口获取图书列表数据
  12. // // const that = this
  13. // axios.get('http://localhost:3000/books')
  14. // .then((ret) => {
  15. // this.books = ret.data
  16. // })
  17. // },
  18. },
  19. created () {
  20. this.loadList()
  21. }

总结:

  1. created方法是vue提供的,页面加载时自动触发
  2. 接口调用采用Promise和Async函数的用法都要掌握

删除图书

目标:实现图书删除功能

  1. async handleDlete (id) {
  2. // 删除图书:根据id调用接口删除图书,删除成功后刷新列表
  3. const res = await axios.delete('books/' + id)
  4. if (res.status === 200) {
  5. // 删除成功,刷新列表
  6. this.loadBookList()
  7. }
  8. },

总结:绑定事件;获取要删除的图书的id;调用接口删除;判断返回的状态并刷新列表

添加图书

目标:实现添加图书功能

  • 绑定按钮事件
  • 获取表单数据
  • 调用接口添加图书
  • 根据返回的状态刷新列表
  • 清空表单
  1. async handleSubmit () {
  2. // 把表单中输入的数据添加到数组中
  3. if (!this.bookname) {
  4. alert('请输入图书名称')
  5. return
  6. }
  7. // 调用接口添加图书
  8. const ret = await axios.post('books', book)
  9. if (ret.status === 201 && ret.data.id) {
  10. // 添加成功,刷新列表,清空表单
  11. this.loadBookList()
  12. this.bookname = ''
  13. this.author = ''
  14. } else {
  15. alert('添加图书失败')
  16. }
  17. }
  18. }

总结:绑定事件;获取表单数据;表单验证;调用接口添加图书;返回状态判断;刷新列表清空表单。

搜索图书

目标:掌握vue侦听器功能

侦听器应用场景:在需要监听某项数据的变化然后去,做异步操作或开销较大操作(逻辑非常复杂)。

补充:凡是以后需要监听数据的变化而去做一些事情,就可以使用侦听器。

  • 基本用法
  1. // 侦听器基本用法
  2. const vm = new Vue({
  3. el: '#app',
  4. data: {
  5. msg: 'Jim Green',
  6. firstname: 'Jim',
  7. lastname: 'Green'
  8. },
  9. watch: {
  10. // 侦听器(监听msg属性的变化)
  11. // msg(newMsg, oldMsg) {
  12. // // newMsg表示msg被修改后的值
  13. // // oldMsg表示msg被修改前的值
  14. // console.log(newMsg, oldMsg)
  15. // }
  16. firstname(v) {
  17. this.msg = v + ' ' + this.lastname
  18. },
  19. lastname(v) {
  20. this.msg = this.firstname + ' ' + v
  21. }
  22. }
  23. })

总结:

  1. 侦听器本质是函数,但是函数名称需要和侦听的属性名称标尺一致
  2. 侦听器函数的形参有两个,分别是:修改后的值和修改前的值
  • 基于侦听器实现搜索功能
  1. <input v-model='keyword' type="text" class="form-control" style="width:200px;display: inline-block;" placeholder="请输入搜索关键字">
  1. data: {
  2. // 品牌列表
  3. list: [],
  4. // 品牌名称
  5. bookname: '',
  6. // 搜索关键字
  7. keyword: ''
  8. },
  1. watch: {
  2. async keyword (v) {
  3. // 发送请求:根据关键字查询后端接口数据
  4. const ret = await axios.get('books', {
  5. params: {
  6. // username后面添加_like表示模糊匹配
  7. // _like其实是json-server规定的
  8. bookname_like: v
  9. }
  10. })
  11. // 把匹配的结果覆盖默认列表数据
  12. this.books = ret.data
  13. }
  14. }

总结:

  1. 基于侦听器方式监听输入的关键字的变化
  2. 调用接口是通过参数的_like实现模糊匹配,规则由json-server规定