- 子组件$emit触发父组件绑定的的方法并传递数据。
this.$emit('事件',value:参数) //子传父
- 父组件调用子组件的方法,可以传递数据。
this.$refs.子组件的ref.子组件的方法
- 兄弟组件之间相互传递数据。//只要不是父子关系的都是兄弟关系
$on
- 给多选按钮绑定chang事件当选中或者取消选中的时候就会触发这个事件,然后可以通过e.target.checked的值true或者false来确定是否选中.
<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isfull"
@change="fullchange"/>
methods:{
//e代表事件对象,此处e:input框
change(e){
this.$emit('fullchange',e.target.checked)
}
- 1.@click,2.@input,2.自定义事件接收到的值被覆盖,三种情况考虑$event
头条案例
- views文件夹与components的区别
views:组件通过路由来切换
components:组件不是通过路由来切换,该组件可复用
当使用第三方组件库时,有修改第三方组件默认样式的需求,需要用到/deep/
文件夹utils:放工具模块
例:request.js:封装api请求
购物车案例1
工程组件化
代码书写规范:
指令
属性绑定
绑定事件
//app.vue
<template>
<div class="app-container">
<Header title="购物车案例"/>
<!--循环渲染每一个商品的信息-->
<Goods v-for="item in list" :key="item.id" :title="item.goods_name"
:pic="item.goods_img" :price="item.goods_price" :state="item.goods_state"/>
</div>
</template>
<script>
//导入axios请求库,调用方法拿数据
import axios from 'axios'
//导入需要的组件
import Header from "./components/Header/Header";
import Goods from "./components/Goods/Goods";
export default {
data(){
return{
//用来存购物车的列表数据,默认为空数组
list:[]
}
},
methods:{
//封装请求列表数据的方法
async initCartList() {
//调用axios的get方法,返回值为promise
//将axios请求封装到一个函数中,一会到created里面调用这个函数
//结构赋值axios中有六个属性,其中我们需要的是data属性,重命名为res
const {data: res} = await axios.get('https://www.escook.cn/api/cart')
if (res.status === 200) {
this.list = res.list
}
}
},
created() {
//调用请求数据的方法
this.initCartList()//组件在内存中一被创建好,就调用initCartList()方法,执行方法中的代码开始请求数据
//只要请求回来的数据,在页面渲染中需要用到,就必须转存到data中
},
components:{
Header,Goods
}
}
</script>
<style lang="less">
.app-container{
padding-top: 45px;
padding-bottom: 50px;
}
</style>
//Header.vue
<template>
<div class="header-container">{{title}}</div>
</template>
<script>
export default {
props:{
title:{
default:'',
type:String
}
}
}
</script>
<style lang="less" scoped>
.header-container {
font-size: 12px;
height: 45px;
width: 100%;
background-color: #1d7bff;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
position: fixed;
top: 0;
}
</style>
//Goods.vue
<template>
<div class="goods-container">
<!-- 左侧图片 -->
<div class="thumb">
<div class="custom-control custom-checkbox">
<!-- 复选框 勾选状态-->
<input type="checkbox" class="custom-control-input" id="cb1" :checked="state" />
<label class="custom-control-label" for="cb1">
<!-- 商品的缩略图 -->
<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>
<!-- 商品的数量 -->
</div>
</div>
</div>
</template>
<script>
export default {
props:{
//要渲染的商品的标题
title:{
default:'',
type:String
},
pic:{
default: '',
type:String
},
price:{
default:'',
type:Number
},
//商品的勾选状态
state:{
default:true,
type:Boolean
}
}
}
</script>
<style lang="less" scoped>
.goods-container {
.goods-container {
border-top: 1px solid #efefef;
}
padding: 10px;
display: flex;
.thumb {
display: flex;
align-items: center;
img {
width: 100px;
height: 100px;
margin: 0 10px;
}
}
.goods-info {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.goods-title {
font-weight: bold;
font-size: 12px;
}
.goods-info-bottom {
display: flex;
justify-content: space-between;
.goods-price {
font-weight: bold;
color: red;
font-size: 13px;
}
}
}
}
</style>
- 封装props的两种方法
props:{
//一个个属性封装,一个个传
title:{
default:'',
type:String
},
pic:{
default: '',
type:String
},
price:{
default:'',
type:Number
},
//商品的勾选状态
state:{
default:true,
type:Boolean
}
}
//传数据
<Goods v-for="item in list" :key="item.id" :title="item.goods_name"
:pic="item.goods_img" :price="item.goods_price" :state="item.goods_state"/>
props:{
//封装一个goods对象
goods:{
type:object,
default:{}
}
//传数据
<Goods v-for="item in list" :key="item.id" :goods="item"/>
//此时
<h6 class="goods-title">{{goods.goods_title}}</h6>
<div class="goods-info-bottom">
<!-- 商品价格 -->
<span class="goods-price">{{goods.goods_price}}¥</span>
为什么用v-bind绑定state而不用v-model:因为state是props而不是form,是只读的
结论:在做项目开发时不要传一整个对象过来,最好把一个一个属性分开传过来,更加通用
改进1:实现监听Goods中商品复选框变化,将值同步到App中
- 子传父(自定义事件):将子组件Goods中的变化传到父组件App中
子组件item项中是否勾选要同步到父组件的数组中
还要确定是哪个商品要勾选
所以需传值:商品Id和商品勾选状态
- 在子组件中,要监听复选框状态变化的事件,拿到最新的勾选状态
只要复选框的勾选状态发生变化,自动触发change事件
<input type="checkbox" @change="stateChange"/>
- 当监听到勾选状态发生变化后,应该立即把最新状态,通过自定义事件的形式发送给父组件
this.$emit('stateChange',{id,value})
id:商品id,value:商品最新勾选状态
- e.target是目标对象,如点击事件就是点击的DOM元素。e.target.value就是目标对象的值。
- e.event是目标所发生的事件。
- this返回的是绑定事件的对象(元素)
//Goods.vue
<template>
<!-- 复选框 勾选状态-->
<input type="checkbox" class="custom-control-input" id="cb1" :checked="state"
@change="stateChange"/> //自定义事件
</template>
<script>
export default {
props:{
//商品的Id
id:{
//require:true,必须传商品的id,否则报错
require:true,
type:Number
},
//要渲染的商品的标题
title:{
default:'',
type:String
},
pic:{
default: '',
type:String
},
price:{
default:'',
type:Number
},
//商品的勾选状态
state:{
default:true,
type:Boolean
}
},
methods:{
//只要复选框的选择状态发生变化,就会调用这个处理函数
stateChange(e)
{//声明一个变量newState
const newState = e.target.checked
//通过勾选状态触发自定义事件state-change 参数为id,newstate
this.$emit('state-change',{id:this.id, value:newState})
}
}
}
</script>
//App.vue
<template>
<div class="app-container">
<Header title="购物车案例"/>
<!--循环渲染每一个商品的信息-->
<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"
@state-change="getNewState"/>
//通过自定义事件state-change监听
</div>
</template>
<script>
//导入axios请求库,调用方法拿数据
import axios from 'axios'
//导入需要的组件
import Header from "./components/Header/Header";
import Goods from "./components/Goods/Goods";
export default {
data(){
return{
//用来存购物车的列表数据,默认为空数组
list:[]
}
},
methods:{
getNewState(val){
//接受子组件传来的数据
}
}
}
</script>
改进2:实现运用Goods子组件传给App父组件的id在list[]数组中查找对应商品,查找到对应商品就将其勾选状态改成传来的value的状态
id:Goods->App(子传父)
App中获取了商品信息的数据,但是商品的属性(id,goods_price等都在Goods中,所以要从Goods传给App)
全选状态:App->Footer(父传子)
//App.vue
<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"
@state-change="getNewState"/>
<Footer :isfull="fullstate"></Footer>
getNewState(val){
//接受子组件传来的数据
this.list.some(item =>{
if (item.id === val.id){
//终止后续循环
item.goods_state = val.value
return true
}
})
},
computed:{
//动态计算出全选的状态是true还是false
fullstate(){
//每个商品的状态是否都为true,如果全都为true则返回true否则返回false
return this.list.every( item => item.goods_state === true)
}
//Footer.vue
<template>
<div class="footer-container">
<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isfull" />
</div>
</template>
<script>
export default {
props:{
//全选的状态
isfull:{
type:Boolean,
default:false
}
}
}
</script>
改进4:Footer组件实现全选状态,全选框勾选:所有商品勾选;全选框不勾选:所有商品不勾选
实现子组件(Footer)状态变化传到父组件(App)中的每一件商品
监听Footer复选框的change事件,拿到状态变化,子传父(通过自定义事件)传给父组件
父组件用子组件Footer时绑定事件监听,拿到最新状态
Foreach循环
//Footer组件
<input type="checkbox" class="custom-control-input" id="cbFull" :checked="isfull"
@change="fullchange"/>
methods:{
fullchange(e){//方法名
this.$emit('full_change',e.target.checked)//自定义事件,全选复选框是否勾选
}
}
//App.vue
<Footer :isfull="fullstate" @full_change="getFullState"></Footer>
getFullState(val){
this.list.forEach(item=>(item.goods_state=val))//每个商品的状态都与全选框状态相同
}
改进5:实现总价功能:将勾选商品的价格加起来
在App中通过计算属性计算出值
父传子(自定义属性)再将值传给子组件Footer
//App.vue
<p>{{totalprice}}</p>
//父传子通过自定义属性
<Footer :isfull="fullstate" @full_change="getFullState" :amount="totalprice"></Footer>
computed:{
//动态计算出全选的状态是true还是false
fullstate(){
//每个商品的状态是否都为true
return this.list.every( item => item.goods_state === true)
},
//已勾选商品的总价格
//1.filter过滤出选中的商品
//2.reduce累加器:reduce((累加结果,累加项)=>{ 计算返回值}
totalprice(){
return this.list.filter(item => item.goods_state).reduce((total,item)=>{
//每次循环都把当前的单价乘以当前的数量,并将值累计到total上,循环完后将total值return出去
return total += item.goods_price * item.goods_count },0)
}
}
//footer.vue
<!-- 中间的合计 -->
<div>
<span>合计:</span>
//.toFixed(2)保存2位小数
<span class="total-price">¥{{ amount.toFixed(2) }}</span>
</div>
</div>
<script>
export default {
props:{
//全选的状态
isfull:{
type:Boolean,
default:false
},
amount:{
type: Number,
default: 0
}
}
}
</script>
改进3:将购买数量传给counter组件
App中获取了商品的数据,App中嵌入了每个商品的item项(Goods.vue),Goods中嵌套了Counter.
现在Counter需要购买数量,数值传递顺序:App->Goods->Counter
App->Goods:父传子(自定义属性)
Goods->Counter:父传子
//App.vue
<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"
@state-change="getNewState" :count="item.goods_count"/>
//Goods.vue
<Counter :num="count"></Counter>
<script>
export default {
props:{
//商品的数量
count:{
default:1,
type:Number
}
}
</script>
//Counter.vue
<!-- 购买的数量 -->
<span class="number-box">{{num}}</span>
<script>
export default {
props:{
//接收到从Goods接收的商品数量的值
num:{
type:Number,
default:1
}
}
}
</script>
改进4:实现点击+,-号按钮(Counter),商品数量+1,-1
数据传递:Counter->Goods->App
Counter->Goods(子传父:自定义事件)
Goods->App(子传父)
传送的数据:
商品Id:加减的是哪个商品的数量//父传子Goods中id传给Counter,告诉counter现在counter中加减的是哪一个 商品
商品的数量(num)//直接Goods->App:使用EventBus方案
//Counter.vue,按钮在counter上
<!-- 减 1 的按钮 -->
<button type="button" class="btn btn-light btn-sm" @click="sub">-</button>
<!-- 购买的数量 -->
<span class="number-box">{{num}}</span>
<!-- 加 1 的按钮 -->
<button type="button" class="btn btn-light btn-sm" @click="add">+</button>
import bus from "../EventBus";
methods:{
add(){
//错误写法:this.num += 1,num为props属性,是只读的
//bus.$emit('share',{id:this.id , value:this.num + 1}) //此处不是修改num的原值,只是传递num+1后的新值
//发送给App的数据格式为:{ id,value }
//写法2
const obj = { id:this.id,value:this.num + 1}
//通过EventBus将obj对象发送给App组件
bus.$emit('share',obj)
},
sub(){
if (this.num - 1 === 0) return
const obj = { id:this.id,value: this.num - 1}
bus.$emit('share',obj)
}
}
//App.vue
import bus from "./components/EventBus";
created() {
//调用请求数据的方法
this.initCartList()//组件在内存中一被创建好,就调用initCartList()方法,执行方法中的代码开始请求数据
//只要请求回来的数据,在页面渲染中需要用到,就必须转存到data中
//val接收传来的数据,格式为:id,value
bus.$on('share',val =>{
this.list.some(item => {
if (item.id === val.id){
item.goods_count = val.value
return true
}
})
})
}
改进5:结算按钮上有购买商品的总数
App->Footer,父传子:自定义属性
//Footer.vue 结算按钮在Footer中
props:{
//全选的状态
isfull:{
type:Boolean,
default:false
},
amount:{
type: Number,
default: 0
},
//已勾选的商品的总数量,由App传过来,即父传子
all:{
type:Number,
default:0
}
}
//App.vue
<Footer :isfull="fullstate" :amount="totalprice" :all="amt" @full_change="getFullState" ></Footer>
computed:{
//动态计算出全选的状态是true还是false
fullstate(){
//每个商品的状态是否都为true
return this.list.every( item => item.goods_state === true)
},
//已勾选商品的总价格
//1.filter过滤出选中的商品
//2.reduce累加器:reduce((累加结果,累加项)=>{ 计算返回值}
totalprice(){
return this.list.filter(item => item.goods_state).reduce((total,item)=>{
//每次循环都把当前的单价乘以当前的数量,并将值累计到total上,循环完后将total值return出去
return total += item.goods_price * item.goods_count },0)//total初始值为0
},
//已勾选的商品总数量
amt(){
return this.list.filter(item => item.goods_state).reduce((t,item)=>{
return t += item.goods_count
},0)
}
}
改进6:将Counter组件改成插槽
改进前:增减商品数量的数据传递 App->Goods->Counter不方便
//App.vue
<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"
@state-change="getNewState"
></Goods>
Goods.vue组件中使用Counter.vue组件
//Goods.vue
<template>
<div class="goods-container">
<!-- 商品标题 -->
<h6 class="goods-title">{{ title }}</h6>
<div class="goods-info-bottom">
<!-- 商品价格 -->
<span class="goods-price">¥{{ price }}</span>
<!-- 商品的数量 -->
<Counter :num="count" :id="id"></Counter>
</div>
</div>
</template>
改进后:
//App.vue
<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"
@state-change="getNewState"
>
<Counter></Counter>
</Goods>
//Goods.vue
<template>
<div class="goods-container">
<span class="goods-price">¥{{ price }}</span>
<!-- 商品的数量 -->
<slot></slot>
</div>
</div>
</template>
改进7:用插槽的方式实现从App中传递商品数量到Counter.vue中
//App.vue
<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"
@state-change="getNewState"
>
<Counter></Counter>
</Goods>
<Goods>标签中进行v-for外循环,<Counter>为内循环,<Counter>内层可以直接访问item项的属性(id,goods_price等)
此时App.vue与Counter.vue传递数据无需传递两次(父传子:App->Goods->Counter)
只需传一次(App->Goods,Counter直接在Goods中接收到数据)
改进后:
//Counter
<span class="number-box">{{ num }}</span>
<script>
export default {
props: {
// 接收到的 num 数量值
num: {
type: Number,
default: 1
}
}
}
</script>
//App
<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"
@state-change="getNewState"
>
//直接再循环中传递
<Counter :num="item.goods_count"></Counter>
</Goods>
改进8:实现点击Counter中的+-按钮改变商品数量
数据传递:Counter->App
改进前:通过eventbus实现兄弟间传数据,需传id,商品数量
//counter
<!-- 减 1 的按钮 -->
<button type="button" class="btn btn-light btn-sm" @click="sub">-</button>
<!-- 购买的数量 -->
<span class="number-box">{{ num }}</span>
<!-- 加 1 的按钮 -->
<button type="button" class="btn btn-light btn-sm" @click="add">+</button>
import bus from "../eventBus";
add() {
// 要发送给 App 的数据格式为 { id, value }
// 其中,id 是商品的 id; value 是商品最新的购买数量
const obj = { id: this.id, value: this.num + 1 }
// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
bus.$emit('share', obj)
},
sub() {
if (this.num - 1 === 0) return
// 要发送给 App 的数据格式为 { id, value }
// 其中,id 是商品的 id; value 是商品最新的购买数量
const obj = { id: this.id, value: this.num - 1 }
// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
bus.$emit('share', obj)
}
改进后:Counter在Goods中,子传父(自定义事件)即可,只需传商品数量
//App.vue
<Counter :num="item.goods_count" @num-change="getNewNum(item,$event)"></Counter>
<!--getNewNum(item):直接传item,而不传item.id:因为这里的item就是改好数据的item,而我们要改的数据就是item就不用传id了
我们根据id(即索引)找到需要修改的对象(即item项),此处我们要修改的就是item-->
</Goods>
//@num-change="getNewNum没有定义参数时,默认接收到参数e
//@num-change="getNewNum(item),默认参数e被item覆盖
//@num-change="getNewNum(item,$event),传入的第一个参数为item,第二个参数为默认参数e
//1.@click,2.@input,2.自定义事件接收到的值被覆盖,三种情况考虑$event
getNewNum(item,val){
item.goods_count = val
}
//Counter.vue
methods: {
// 点击按钮,数值 +1
add() {
this.$emit('num-change',this.num + 1) //自定义事件
},
sub() {
this.$emit('num-change',this.num - 1)
}
}
版权归原作者 一如远行客 所有, 如有侵权,请联系我们删除。