今日学习目标
✔ 能够说出 Vue 组件生命周期。
✔ 能够掌握 Axios 的使用。
✔ 能够完成购物车案例开发。
Vue 生命周期
目标
了解生命周期的概念。
人的生命周期
组件生命周期
一个组件从创建到销毁的整个过程就是生命周期。
小结
组件生命周期是什么?
答案
Vue 钩子函数
目标
掌握组件的生命周期阶段对应的钩子函数。
讲解
官网文档
作用:生命周期阶段对应的钩子函数会自动执行,我们可以在函数内部指定特定的操作。
分类:4 大阶段 8 个方法。
阶段 |
方法名 |
方法名 |
初始化 |
beforeCreate |
created |
挂载 |
beforeMount |
mounted |
更新 |
beforeUpdate |
updated |
销毁 |
beforeDestroy |
destroyed |
小结
Vue 组件生命周期有几个阶段,几个函数,分别是什么?
答案
Vue 初始化阶段
目标
掌握初始化阶段 2 个钩子函数的作用和执行时机。
讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div>Life</div> </template> <script> export default { name: 'LifeComponent', data() { return { msg: 'Hello,Vue', } }, beforeCreate() { console.log(this.msg) }, created() { }, } </script>
|
小结
Vue 初始化阶段执行了哪些钩子函数?
答案
你觉得在哪个阶段发请求比较合适,为什么?
答案
Vue 挂载阶段
目标
掌握挂载阶段 2 个钩子函数的作用和执行时机。
讲解
components/Life.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div> <p>学习生命周期 - 看控制台打印</p> <p id="myP">{{ msg }}</p> </div> </template>
<script> export default { data() { return { msg: 'Hello World', } }, beforeMount() { console.log(document.getElementById('myP')) }, mounted() { console.log(document.getElementById('myP').innerHTML) }, } </script>
|
小结
Vue 实例从创建到渲染完成都经历了哪些钩子函数?
答案
- beforeCreate / created / beforeMount / mounted
哪个阶段能获取到真实 DOM?
答案
Vue 更新阶段
目标
掌握更新阶段 2 个钩子函数作用和执行时机。
讲解
components/Life.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div> <ul id="myUL"> <li v-for="(val, index) in arr" :key="index">{{ val }}</li> </ul> <button @click="arr.push(1000)">点击末尾加值</button> </div> </template>
<script> export default { data() { return { arr: [5, 8, 2, 1], } }, beforeUpdate() { console.log(document.querySelectorAll('#myUL>li')[4]) }, updated() { console.log(document.querySelectorAll('#myUL>li')[4].innerHTML) }, } </script>
|
小结
什么时候执行 updated 钩子函数?
答案
Vue 销毁阶段
目标
掌握销毁阶段 2 个钩子函数的作用和执行时机。
讲解
触发时机:组件卸载/销毁时会触发 beforeDestroy 和 destroyed。
应用场景:解绑事件或清理定时器。
- 演示销毁。
components/Life.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <div>Life</div> </template> <script> export default { name: 'LifeComponent', beforeDestroy() { console.log('~') }, destroyed() {}, } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div> <Life v-if="bBar" /> <button @click="bBar = !bBar">销毁组件</button> </div> </template>
<script> import Life from './components/Life.vue'
export default { name: 'App', data() { return { bBar: true, } }, components: { Life, }, } </script>
|
- 演示应用场景。
components/Life.vue
,清理定时器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div>Life</div> </template> <script> export default { name: 'LifeComponent', created() { this.timer = setInterval(() => { console.log('~') }, 1000) }, beforeDestroy() { clearInterval(this.timer) }, } </script>
|
components/Life.vue
,解绑事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div>Life</div> </template> <script> export default { name: 'LifeComponent', created() { document.documentElement.addEventListener('click', this.handleClick) }, methods: { handleClick() { console.log('~') }, }, beforeDestroy() { document.documentElement.removeEventListener('click', this.handleClick) }, } </script>
|
小结
一般在 beforeDestroy/destroyed 里做什么?
答案
Axios 基本使用
目标
了解什么是 Axios,以及如何使用。
讲解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| axios({ method: '请求方式', url: '请求地址', data: { xxx: xxx, }, params: { xxx: xxx, }, }) .then((res) => { console.log(res.data) }) .catch((err) => { console.log(err) })
|
小结
Axios 是什么?
答案
Axios 获取图书
目标
调用获取所有图书信息的接口。
讲解
components/UseAxios.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <div> <p>获取所有图书信息</p> <button @click="getAllFn">点击-查看控制台</button> </div> </template>
<script> import axios from 'axios' export default { methods: { getAllFn() { axios({ method: 'GET', url: 'http://123.57.109.30:3006/api/getbooks', }).then((res) => { console.log(res) }) }, }, } </script>
|
小结
axios 函数调用返回的结果是什么?
答案
如何拿到 Promise 里 AJAX 的成功或失败的结果?
答案
Axios 传递参数
目标
调用接口,获取某本书籍信息。
讲解
components/UseAxios.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div> <p>查询某本书籍信息</p> <input type="text" placeholder="请输入要查询 的书名" v-model="bName" /> <button @click="findFn">查询</button> </div> </template>
<script> import axios from 'axios' export default { data() { return { bName: '', } }, methods: { findFn() { axios({ url: 'http://123.57.109.30:3006/api/getbooks', method: 'GET', params: { bookname: this.bName, }, }).then((res) => { console.log(res) }) }, }, } </script>
|
小结
通过 Axios 哪个配置项会把参数自动写到 url?
后面?
答案
Axios 发布书籍
目标
完成发布书籍功能。
讲解
components/UseAxios.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <template> <div> <p>新增图书信息</p> <div> <input type="text" placeholder="书名" v-model="bookObj.bookname" /> </div> <div> <input type="text" placeholder="作者" v-model="bookObj.author" /> </div> <div> <input type="text" placeholder="出版社" v-model="bookObj.publisher" /> </div> <button @click="sendFn">发布</button> </div> </template>
<script> import axios from 'axios' export default { data() { return { bName: '', bookObj: { bookname: '', author: '', publisher: '', }, } }, methods: { sendFn() { axios({ url: 'http://123.57.109.30:3006/api/addbook', method: 'POST', data: { appkey: '7250d3eb-18e1-41bc-8bb2-11483665535a', ...this.bookObj, }, }) }, }, } </script>
|
小结
Axios 哪个选项,可以把参数自动装入到请求体中?
答案
Axios 全局配置
目标
统一设置请求前缀/基准地址。
讲解
1 2 3 4 5 6 7 8 9 10 11
| axios.defaults.baseURL = "http://123.57.109.30:3006"
getAllFn() { axios({ url: "/api/getbooks", method: "GET", }).then((res) => { console.log(res); }); },
|
小结
Axios 如何配置基地址?
答案
购物车案例
项目初始化
目标
初始化新项目,清空不要的东西,下载 bootstrap 库,下载 less 模块。
讲解
1 2 3
| vue create shopcar yarn add bootstrap yarn add less less-loader -D
|
按照需求,把项目页面拆分成几个组件,在 components 文件夹下创建:MyHeader、MyFooter、MyGoods、MyCount 组件。
在 App.vue 中引入上面的组件并注册。
在 main.js 中引入 bootstrap 库。
1
| import 'bootstrap/dist/css/bootstrap.css'
|
components/MyHeader.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="my-header">购物车案例</div> </template>
<script> export default {} </script>
<style lang="less" scoped> .my-header { height: 45px; line-height: 45px; text-align: center; background-color: #1d7bff; color: #fff; position: fixed; top: 0; left: 0; width: 100%; z-index: 2; } </style>
|
components/MyGoods.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <template> <div class="my-goods-item"> <div class="left"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" id="input" /> <label class="custom-control-label" for="input"> <img src="http://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="" /> </label> </div> </div> <div class="right"> <div class="top">商品名字</div> <div class="bottom"> <span class="price">¥ 100</span> <span> 数量组件 </span> </div> </div> </div> </template>
<script> export default {} </script>
<style lang="less" scoped> .my-goods-item { display: flex; padding: 10px; border-bottom: 1px solid #ccc; .left { img { width: 120px; height: 120px; margin-right: 8px; border-radius: 10px; } .custom-control-label::before, .custom-control-label::after { top: 50px; } } .right { flex: 1; display: flex; flex-direction: column; justify-content: space-between; .top { font-size: 14px; font-weight: 700; } .bottom { display: flex; justify-content: space-between; padding: 5px 0; align-items: center; .price { color: red; font-weight: bold; } } } } </style>
|
components/MyCount.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <div class="my-counter"> <button type="button" class="btn btn-light">-</button> <input type="number" class="form-control inp" /> <button type="button" class="btn btn-light">+</button> </div> </template>
<script> export default {} </script>
<style lang="less" scoped> .my-counter { display: flex; .inp { width: 45px; text-align: center; margin: 0 10px; } .btn, .inp { transform: scale(0.9); } } </style>
|
components/MyFooter.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <template> <div class="my-footer"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" id="footerCheck" /> <label class="custom-control-label" for="footerCheck">全选</label> </div> <div> <span>合计:</span> <span class="price">¥ 0</span> </div> <button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button> </div> </template>
<script> export default {} </script>
<style lang="less" scoped> .my-footer { position: fixed; z-index: 2; bottom: 0; width: 100%; height: 50px; border-top: 1px solid #ccc; display: flex; justify-content: space-between; align-items: center; padding: 0 10px; background: #fff;
.price { color: red; font-weight: bold; font-size: 15px; } .footer-btn { min-width: 80px; height: 30px; line-height: 30px; border-radius: 25px; padding: 0; } } </style>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div class="app"> <MyHeader /> <MyGoods /> <MyFooter /> </div> </template>
<script> import MyHeader from './components/MyHeader.vue' import MyGoods from './components/MyGoods.vue' import MyFooter from './components/MyFooter.vue' export default { name: 'App', components: { MyHeader, MyGoods, MyFooter, }, } </script>
<style scoped> .app { padding-top: 45px; } </style>
|
小结
拿到项目需求,做什么?
答案
- 新建项目,下载需要的包。
- 分析,拆分,创建组件。
- 引入组件到对应的位置并注册。
头部自定义
目标
头部的标题,颜色,背景色可以随便修改。
讲解
在 MyHeader.vue
的 props 中准备变量,然后使用。
在父组件传入相应的值 (color 和 backgroundColor)。
MyHeader.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <template> <div class="my-header" :style="{ backgroundColor: background, color }">{{ title }}</div> </template>
<script> export default { props: { background: String, color: { type: String, default: '#fff', }, title: { type: String, required: true, }, }, } </script>
<style lang="less" scoped> .my-header { height: 45px; line-height: 45px; text-align: center; background-color: #1d7bff; color: #fff; position: fixed; top: 0; left: 0; width: 100%; z-index: 2; } </style>
|
App.vue
传入相应自定义的值。
1
| <MyHeader title="购物车案例"></MyHeader>
|
props: []
,只能接收外部传入的数据。
props: {}
,能对接收到的数据进行校验。
小结
封装组件,如何个性化可能变化的部分?
答案
props 有哪 2 种定义方式,区别是?
答案
- props: [],只能接收外部传入的数据。
- props: {},能对接收到的数据进行校验。
请求数据
目标
使用 Axios 把数据请求回来。
讲解
数据地址:https://www.escook.cn/api/cart (GET 方式)。
- 下载 axios。
- 原型上挂载 axios,
main.js
。
1 2 3 4 5 6 7 8
| import axios from 'axios' axios.defaults.baseURL = 'https://www.escook.cn'
Vue.prototype.$axios = axios
new Vue({ render: (h) => h(App), }).$mount('#app')
|
App.vue
请求使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> export default { data() { return { list: [], } }, created() { this.$axios({ url: '/api/cart', }).then((res) => { console.log(res) this.list = res.data.list }) }, } </script>
|
小结
在哪里定义的数据,任意组件都可以通过 this 访问到?
答案
数据渲染
目标
把上面请求的数据,铺设到页面上。
讲解
App.vue
1
| <MyGoods v-for="obj in list" :key="obj.id" :gObj="obj"></MyGoods>
|
MyGoods.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div class="my-goods-item"> <div class="left"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" :id="gObj.id" v-model="gObj.goods_state" /> <label class="custom-control-label" :for="gObj.id"> <img :src="gObj.goods_img" alt="" /> </label> </div> </div> <div class="right"> <div class="top">{{ gObj.goods_name }}</div> <div class="bottom"> <span class="price">¥ {{ gObj.goods_price }}</span> <span> <MyCount :obj="gObj"></MyCount> </span> </div> </div> </div> </template>
<script> import MyCount from './MyCount' export default { props: { gObj: Object, }, components: { MyCount, }, } </script>
|
MyCount.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <div class="my-counter"> <button type="button" class="btn btn-light">-</button> <input type="number" class="form-control inp" v-model.number="obj.goods_count" /> <button type="button" class="btn btn-light">+</button> </div> </template>
<script> export default { props: { obj: Object, }, } </script>
|
小结
拿到数据如何铺设页面?
答案
商品选中
目标
点击发现总是第一个被选中。
步骤
lable 的 for 值对应 input 的 id,点击 label 就能让对应 input 处于激活。
1 2 3 4
| <input type="checkbox" class="custom-control-input" :id="gObj.id" v-model="gObj.goods_state" /> <label class="custom-control-label" :for="gObj.id"> <img :src="gObj.goods_img" alt="" /> </label>
|
小结
label 标签有什么用?
答案
- 配合 for 属性及表单的 id 属性,能扩大表单的点击区域。
组件传递数据,传递对象本质上传的是什么?
答案
数量控制
目标
点击 + 和 - 或者直接修改输入框的值影响商品购买的数量。
讲解
components/MyCount.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <div class="my-counter"> <button type="button" class="btn btn-light" :disabled="obj.goods_count === 1" @click="obj.goods_count > 1 && obj.goods_count--">-</button> <input type="number" class="form-control inp" v-model.number="obj.goods_count" /> <button type="button" class="btn btn-light" @click="obj.goods_count++">+</button> </div> </template>
<script> export default { props: { obj: Object, }, watch: { obj: { deep: true, handler() { if (this.obj.goods_count < 1) { this.obj.goods_count = 1 } }, }, }, } </script>
|
小结
子组件改变对象里属性会影响外面数组里对象否?
答案
控制输入框值合法性?
答案
全选功能
目标
在底部组件上,完成全选功能。
讲解
确定当前全选按钮的状态。
点击全选状态后,把改变后的新值同步给每一个单选。
MyFooter.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <template> <div class="my-footer"> <div class="custom-control custom-checkbox"> <input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll" /> <label class="custom-control-label" for="footerCheck">全选</label> </div> <div> <span>合计:</span> <span class="price">¥ {{ allPrice }}</span> </div> <button type="button" class="footer-btn btn btn-primary">结算 ( {{ allCount }} )</button> </div> </template>
<script> export default { props: { list: Array, }, computed: { isAll: { set(val) { this.$emit('changeAll', val) }, get() { return list.arr.every((obj) => obj.goods_state === true) }, }, }, } </script>
|
App.vue
1 2 3 4 5 6 7 8 9
| <MyFooter @changeAll="allFn" :list="list"></MyFooter>
<script> methods: { allFn(bool){ this.list.forEach(obj => obj.goods_state = bool) } } </script>
|
小结
全选和单选,互相影响的思路是什么?
答案
- 根据所有单选按钮的状态来确定全选按钮的状态。
- 点击全选状态后,把改变后的新值同步给每一个单选。
总数量
目标
完成底部组件,显示选中的商品的总数量。
讲解
MyFooter.vue
1 2 3 4 5 6 7 8
| allCount() { return this.list.reduce((acc, cur) => { if (cur.goods_state === true) { acc += cur.goods_count } return acc }, 0) },
|
小结
统计选中商品的总数量思路是什么?
答案
总价
目标
完成选中商品计算价格。
讲解
components/MyFooter.vue
1 2 3 4 5 6 7 8
| allPrice(){ return this.arr.reduce((acc, cur) => { if (cur.goods_state){ acc += cur.goods_count * cur.goods_price } return acc; }, 0) }
|
小结
统计总价如何做的?
答案
- 计算属性。
- 统计数组里的商品数量 * 商品单价。
- 只要依赖项修改,计算属性都会重新计算总价。
今日作业
课上案例
图书管理
获取
参数名称 |
参数类型 |
是否必选 |
参数说明 |
id |
Number |
否 |
图书 Id |
bookname |
String |
否 |
图书名称 |
author |
String |
否 |
作者 |
publisher |
String |
否 |
出版社 |
appkey |
String |
否 |
个人 ID |
1 2 3 4 5 6 7 8 9 10
| { "status": 200, "msg": "获取图书列表成功", "data": [ { "id": 1, "bookname": "西游记", "author": "吴承恩", "publisher": "北京图书出版社" }, { "id": 2, "bookname": "红楼梦", "author": "曹雪芹", "publisher": "上海图书出版社" }, { "id": 3, "bookname": "三国演义", "author": "罗贯中", "publisher": "北京图书出版社" } ] }
|
添加
- 请求方式:POST。
- 请求地址:根域名/api/addbook。
- 请求参数
参数名称 |
参数类型 |
是否必选 |
参数说明 |
bookname |
String |
是 |
图书名称 |
author |
String |
是 |
作者 |
publisher |
String |
是 |
出版社 |
appkey |
String |
是 |
个人 ID - 用’7250d3eb-18e1-41bc-8bb2-11483665535a’ |
1 2 3 4 5 6 7 8 9 10
| { "status": 201, "data": { "author": "施大神" "bookname": "水浒传2" "id": 41 "publisher": "未来出版社" } "msg": "添加图书成功" }
|