目录
3.4 this.$nextTick(cb)方法---代码需要等待页面渲染完成后执行
一、组件的生命周期
1.1 生命周期&生命周期函数
生命周期是指一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段。
生命周期函数:由vue框架提供的内置函数,会伴随着组件的生命周期,自动按序执行。
1.2 组件生命周期函数的分类
1.3 生命周期图示例
VUE官网生命周期图--------------------Vue 实例 — Vue.js
created生命周期函数,非常常用,经常在它里面,调用methods中的方法,请求服务器的数据。并且把请求到的数据,转存到data中,供template模板渲染的时候用
Mounted:该阶段已经把内存中的HTML结构,成功地渲染到了浏览器之中。在Mounted函数中最早操作DOM元素。
Updated:页面更新后,操作最新的DOM
二、组件之间的数据共享
2.1 组件之间的关系
项目开发中,组件之间最常用的关系有:(1)父子关系(2)兄弟关系
2.1.1 父组件向子组件共享数据
父组件向子组件共享数据需要使用自定义属性。示例代码如下:
npm i 安装项目所需的所有的包
2.1.2 子组件向父组件共享数据
子组件向父组件共享数据使用自定义事件
示例如下
(1)子组件在点击自增时,将自增后的值自定义事件,传递出去
methods: {
add() {
console.log('add')
this.count += 1
this.$emit('numchange',this.count)
}
},
(2)父组件接收。分为触发事件,事件定义两部分
<template>
<div id="left" style="float:left">
<MyDemo @numchange="getNumCount"></MyDemo>
</div>
</template>
<script>
export default {
data() {
return {
countFromSon: 0
}
},
methods: {
getNumCount(val){
this.countFromSon = val
}
}
}
</script>
<style lang="less" scoped>
</style>
2.1.3 兄弟之间的数据共享
在vue2.0x中,兄弟之间数据共享的方案是EventBus
EventBus的使用步骤:
(1)在src/components目录下创建eventBus.js模块,并向外共享一个Vue的实例对象
(2)在数据发送方,d调用bus.$emit('事件名称',要发送的数据)方法触发自定义事件
(3)在数据接收方,调用bus.$on('事件名称',事件处理函数)方法注册一个自定义事件
三、ref引用
3.1 什么是ref引用
ref用来辅助开发者在不依赖于jquery的情况下,获取DOM元素或者组件的引用。
每个vue的组件实例中,都包含一个$ref对象,里面存储着对应的DOM元素或组件的引用。默认情况下,组件的$ref指向一个空对象
3.2 使用ref引用获取DOM元素
总结:ref获取DOM元素分为2步:(1)DOM元素中添加ref属性(2)方法中获取DOM元素,this.$refs.xxx
(1)App.vue中代码如下:
<template>
<div id="app">
<h1>App.vue</h1>
<button @click="print">打印this</button>
</div>
</template>
<script>
export default {
methods: {
print() {
console.log(this)
}
}
}
</script>
<style lang="less" scoped>
</style>
控制台打印this,发现this中存在$refs参数,默认为空值
(2)在h1中添加ref参数,并在methods中打印this.$refs.myh1的值,获取到了DOM元素。此时便可以操作DOM元素,实例中将字体颜色调为红色
<template>
<div id="app">
<h1 ref="myh1">App.vue</h1>
<button @click="print">打印this</button>
</div>
</template>
<script>
export default {
methods: {
print() {
console.log(this.$refs.myh1)
this.$refs.myh1.style.color = "red"
}
}
}
</script>
<style lang="less" scoped>
</style>
3.3 使用ref引用组件实例
如果想使用ref引用页面上的组件实例,方式如下:
应用场景:如下图,app.vue为父组件,left.vue为子组件。left.vue中实现了数据的自增和重置。若此时想实现在app.vue中点击重置,也可以重置left页面的值,就需要先获取left的组件实例。
3.4 this.$nextTick(cb)方法---代码需要等待页面渲染完成后执行
组件的$nextTick(cb)方法,会把cb回调推迟到下一个DOM更新周期之后执行。通俗地来说,等组件的DOM更新完成后,再执行cb回调函数,从而保证cb回调函数可以操作到最新的DOM元素。
如案例按钮和文本框的按需展示:点击按钮后输入框展示,输入框获得焦点后按钮展示。
解决办法:调用$nextTick(cb)方法
等待页面渲染完成,那为什么这里不采用Updated方法呢
分析:inputvisible值默认为false,显示的是按钮。点击按钮,inputvisible值改变为true,文本框显示,触发updated函数,聚焦文本框。文本框聚焦后,触发showButton事件,inputvisible值改变为false,按钮显示。因为inputvisible值改变,触发updated函数,获取文本框DOM失败。
四、数组中的方法
4.1 some循环
在数组中查找值等于某个值的一项。在找到对应的项之后,可以通过return true固定的语法,来终止some循环
forEach用法:无论是否满足条件,都会一直循环下去,无法终止。采用some方法来解决该问题
</script>
const arr = ['zs','ls','ww']
arr.forEach((item,index) => {
console.log('ok')
if(item === 'ls') {
console.log(index)
return true
}
})
</script>
some用法:
</script>
const arr = ['zs','ls','ww']
arr.some((item,index) => {
console.log('ok')
if(item === 'ls') {
console.log(index)
return true
}
})
</script>
4.2 every循环
若item.state值都相等,则result值为true,否则为false
<script>
const arr1 = [
{id: 1, state: true},
{id: 2, state: true},
{id: 3, state: true}
]
const result = arr1.every(item => item.state)
console.log(result)
</script>
4.3 reduce方法
实现用户选中项价格的和。
<script>
const arr1 = [
{id: 1, count: 10, state: true},
{id: 2, count: 20, state: false},
{id: 3, count: 30, state: true}
]
const result = arr1.filter(item => item.state).reduce((amt, item) => {
console.log(amt)
return amt += item.id * item.count
}, 0)
//reduce简写:若只有一行代码,可省去return
const result = arr1.filter(item => item.state).reduce((amt, item) => {amt += item.id * item.count}, 0)
</script>
五、购物车案例
5.1 案例效果
5.2 实现步骤
(1)初始化项目结构
(2)封装MyHeader组件
(3)基于axios请求商品列表数据(GET请求,地址为https://www.escook.cn/api/cart)
(4)封装MyFoot组件
(5)封装MyGoods组件
(6)封装MyCounter组件
5.3 备注
(1)安装并在App.vue中导入axios。
npm i axios -S import axios from 'axios'
(2)在methods方法中,定义initCardList函数请求列表数据
(3)在created生命周期函数中,调用步骤2中封装的initCradList函数
(4)引入Goods.vue组件,v-for将数据循环渲染
(5)每一个商品的标题和图片不同,需要使用props实现
(6)勾选框后后台的state值并未改变,子向父传值,这里用的是自定义事件
(7)count.toFixed(2)保留2位小数
(8)若存在多层嵌套,如孙子像爷爷传值,可直接借助event Bus实现传值
(9)使用eventBus时创建eventBus文件
注:编码规范,绑定时,先指令,再绑定属性,再事件
注:引入组件的时候最好命名为首字母大写,便于和标签区分
5.4 案例详细代码
主要包括Header.vue、Goods.vue、Footer.vue、Counter.vue、App.vue五个页面。
5.4.1 Header.vue
Header.vue负责页面头部,仅涉及组件调用
<template>
<div class="headContainer">
<h5>购物车案例</h5>
</div>
</template>
<script>
export default {
};
</script>
<style lang="less" scoped>
.headContainer {
h5 {
color: cornsilk;
font-weight:bold;
}
background-color: rgb(30,122,255);
text-align: center;
padding: 10px;
}
</style>
5.4.2 Footer.vue
Footer.vue负责页面底部。页面主要包含全选按钮、合计金额、结算的商品数量三部分。涉及父子组件之间的传值。
<template>
<div class="footer-container">
<div>
<div class="check">
<!-- 调用change事件,当chexkbox的值改变时,执行fullChange事件,
将全选按钮的选中状态值发送给父组件App.vue,此处是子向父传值-->
<input type="checkbox" :checked="fullflag" @change="fullChange">
<span>全选</span>
</div>
<!-- 结算总金额保留两位小数,采用toFixed函数,父向子传值 -->
<!-- 结算总金额和结算的商品数量是父组件App.vue传过来的,父向子传值 -->
<h5 class="amt">合计:¥{{ amount.toFixed(2) }}</h5>
<button class="count">结算({{ count }})</button>
</div>
</div>
</template>
<script>
export default {
props: {
// 全选按钮是否勾选
fullflag: {
type: Boolean
},
// 商品结算总金额
amount: {
default: 0,
type: Number
},
// 结算商品数量
count: {
default: 0,
type: Number
}
},
methods: {
// 子向父传值,自定义事件
fullChange(e) {
this.$emit('changeFull',{flag: e.target.checked})
}
}
}
</script>
<style lang="less" scoped>
.check, .amt, .count {
float: left;
width: 33%;
}
.count {
background-color: rgb(4,126,249);
border-radius: 8%;
}
</style>
5.4.3 Counter.vue
<template>
<div class="counter-container">
<!-- 减1的按钮 -->
<!-- 执行值减1,并将值传递给组件App.vue,此处采用的是eventBus -->
<button class="btn" @click="minus">-</button>
<!-- 购买的数量 -->
<!-- 购买数量是父组件Goods组件传递过来的 -->
<span class="number-box">{{ count }}</span>
<!-- 加1的按钮,并将值传递给组件App.vue,此处采用的是eventBus-->
<button class="btn" @click="add">+</button>
</div>
</template>
<script>
import bus from '@/components/eventBus.js'
export default {
props: {
// 接收商品的id值,将来使用eventBus方案,把数量传递到App.vue的时候,需要通知App组件,
// 更新的是哪个商品的数量
id: {
type: Number,
required: true
},
// 购买的数量,父组件App.vue赋值
count: {
default: 1,
type: Number
}
},
methods: {
minus() {
// 要发送App的数据格式为{id,value}
// 减去的时候等于0的时候,不能再点
if(this.count - 1 === 0) return
const obj = { id: this.id, value: this.count - 1}
bus.$emit('send',obj)
},
add() {
const obj = { id: this.id, value: this.count + 1}
bus.$emit('send',obj)
}
}
}
</script>
<style lang="less" scoped>
.number-box {
min-width: 30px;
text-align: center;
margin: 0 5px;
}
</style>
5.4.4 Goods.vue
<template>
<div class="goods-container">
<!-- 左侧图片 -->
<div class="thumb">
<div class=" custom-control custom-checkbox">
<!-- 复选框 -->
<input type="checkbox" class="custom-control-input" :id="'cb'+id" :checked="state" @change="stateChange">
<label class="custom-control-lable" :for="'cb'+id">
<img :src="pic" alt="">
</label>
</div>
</div>
<!-- 右侧信息区域 -->
<div class="goods-info">
<!-- 商品标题 -->
<h6 class="goods-title">{{ title }}</h6>
<div class="goods-info-bottom">
<!-- 商品价格 -->
<span class="goods-price">¥{{ price }}</span>
<!-- 数量标签 -->
<Counter :count="count" :id="id"></Counter>
</div>
</div>
<hr>
</div>
</template>
<script>
import Counter from '@/components/Counter/Counter.vue'
export default {
components: {
Counter
},
props: {
id: {
required: true,
type:Number
},
title: {
default: '',
type: String
},
price: {
default: 0,
type: Number
},
pic: {
default: '',
type: String
},
state:{
default: true,
type: Boolean
},
count: {
default: true,
type: Number
}
},
methods: {
stateChange(e) {
const newState = e.target.checked
this.$emit('FstateChange',{id: this.id, value: newState })
}
}
}
</script>
<style lang="less" scoped>
.thumb {
float: left;
width: 50%
}
</style>
5.4.5 App.vue
<template>
<div id="app" class="app-container">
<div>
<Header></Header>
</div>
<Goods v-for="item in list" :key="item.id" :id="item.id" :title="item.goods_name" :pic="item.goods_img" :price="item.goods_price" :state="item.goods_state" :count="item.goods_count" @FstateChange="getState"></Goods>
<Footer :fullflag="fullstate" :amount="amt" :count="count" @changeFull="changeAll"></Footer>
</div>
</template>
<script>
import axios from 'axios'
import Header from './components/Header/Header.vue'
import Goods from './components/Goods/Goods.vue'
import Footer from './components/Footer/Footer.vue'
import bus from '@/components/eventBus.js'
export default {
components: {
Header,
Goods,
Footer
},
data() {
return {
list: []
};
},
computed: {
// 动态计算出全选的状态是true还是false
fullstate() {
return this.list.every(item => item.goods_state === true)
},
amt() {
console.log(this.list)
return this.list.filter(item => item.goods_state).reduce((amt, item) => {
return amt += item.goods_price * item.goods_count
},0)
},
count() {
return this.list.filter(item => item.goods_state).reduce((count, item) => {
return count += item.goods_count
},0)
}
},
created() {
this.initCardList()
this.getCount()
},
methods: {
// 发送axios请求获取数据
async initCardList() {
const {data : res} = await axios.get('https://www.escook.cn/api/cart')
this.list = res.list
console.log(this.list)
},
// 接收子组件传过来的勾选框值,并更新
getState(val) {
this.list.some( item => {
if(item.id === val.id) {
item.goods_state = val.value
return true
}
})
},
// 全选或者全不选时将值传给每一个商品的多选框
changeAll(val){
this.list.forEach((item,index) => {
item.goods_state = val.flag
})
},
// 接收Counter.vue点击新增或减去按钮后的counter值
getCount() {
bus.$on('send',val => {
this.list.some(item => {
if(item.id === val.id) {
item.goods_count = val.value
return true
}
})
})
}
},
}
</script>
<style lang="less" scoped>
Header {
padding-top: 0%;
position: absolute;
}
Footer {
position: fixed;
bottom:0;
}
</style>
5.3.6 eventBus.js
import Vue from 'vue'
export default new Vue()
5.5 在Vue中使用Bootstrap
(1)终端执行npm install bootstrap@3 安装包
(2)然后在Vue项目中的main.js引入
import Vue from 'vue'
import App from './App'
// 引入BootStrap
import 'bootstrap/dist/css/bootstrap.min.css'
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})