- 能够理解Vue组件概念和作用
- 能够掌握封装创建组件能力
- 能够使用组件之间通信
- 能够完成todo案例
一.Vue组件创建和使用
1.折叠面板-实现多个
创建一个文件夹demo 具体步骤请参考vue.js---vue基础
⚫ 解决方案: 采用vue提供的单.vue文件-组件方式来封装一套然后复用
在components文件夹下创建组件PanelComponent.vue
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="isShow = !isShow">
{{ isShow ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="isShow">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
};
},
};
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}</style>
App.vue
<template>
<div id="app">
<h3>案例:折叠面板</h3>
<Pannel></Pannel>
<Pannel></Pannel>
<Pannel></Pannel>
</div>
</template>
<script>
import Pannel from './components/PanelComponent.vue'
export default {
components: {
Pannel: Pannel
}
}
</script>
<style lang="less">
body {
background-color: #ccc;
#app {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
h3 {
text-align: center;
}
}
}
</style>
下载less,less-loader第三方库
npm install less-loader --save-dev
启动服务器,打开网站
1. 遇到重复标签想复用?
封装成组件
2. 组件好处? 各自独立, 便于复用
2.组件概念
- 组件是可复用的 Vue 实例, 封装标签, 样式和JS代码
- 组件化 :封装的思想,把页面上 `可重用的部分` 封装为 `组件`,从而方便项目的 开发 和 维护
- 一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)
组件是什么? 可复用的vue实例, 封装标签, 样式, JS
什么时候封装组件? 遇到重复标签, 可复用的时候
组件好处? 各自独立, 互不影响
3.组件_基础使用
目标:每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件
创建组件, 封装要复用的标签, 样式, JS代码
2. 注册组件
- 全局注册 – main.js中 – 语法如图
- 局部注册 – 某.vue文件内 – 语法如图
3. 使用组件
文件demo\src\components\PanelComponent_1.vue
<template>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="isShow = !isShow">
{{ isShow ? "收起" : "展开" }}
</span>
</div>
<div class="container" v-show="isShow">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true,
};
},
};
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
</style>
App.vue
<template>
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 4. 组件名当做标签使用 -->
<!-- <组件名></组件名> -->
<PannelG></PannelG>
<PannelL></PannelL>
</div>
</template>
<script>
// 目标: 局部注册 (用的多)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/PanelComponent_1.vue'
export default {
// 3. 局部 - 注册组件
/*
语法:
components: {
"组件名": 组件对象
}
*/
components: {
PannelL: Pannel
}
}
</script>
<style lang="less">
body {
background-color: #ccc;
#app {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
h3 {
text-align: center;
}
}
}
</style>
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 目标: 全局注册 (一处定义到处使用)
// 1. 创建组件 - 文件名.vue
// 2. 引入组件
import Pannel from './components/PanelComponent_1.vue'
// 3. 全局 - 注册组件
/*
语法:
Vue.component("组件名", 组件对象)
*/
Vue.component("PannelG", Pannel)
new Vue({
render: h => h(App),
}).$mount('#app')
创建和使用组件步骤?
创建.vue文件 – 标签 – 样式 – JS进去
注册组件 (全局 / 局部)
使用组件 (组件名用作标签)
组件运行结果? 把组件标签最终替换成, 封装的组件内标签
4.组件-scoped作用
- 准备: 当前组件内标签都被添加 data-v-hash值 的属性
- 获取: css选择器都被添加 [data-v-hash值] 的属性选择器
Vue组件内样式, 只针对当前组件内标签生效如何做? 给style上添加scoped
原理和过程是什么? 会自动给标签添加data-v-hash值属性, 所有选择都带属性选择
二.Vue组件通信
1.组件通信_父传子_props
目标:父组件 -> 子组件 传值
首先明确父和子是谁, 在父引入子 (被引入的是子)
- 父: App.vue
- 子: MyProduct.vue
创建MyProduct.vue如下图所示
子组件内, 定义变量, 准备接收, 然后使用变量
2. 父组件(App.vue)内, 要展示封装的子组件(MyProduct.vue)引入组件, 注册组件, 使用组件, 传值进去
App.vue
<template>
<div>
<!--
目标: 父(App.vue) -> 子(MyProduct.vue) 分别传值进入
需求: 每次组件显示不同的数据信息
步骤(口诀):
1. 子组件 - props - 变量 (准备接收)
2. 父组件 - 传值进去
-->
<Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product>
<Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product>
<Product title="好贵的北京烤鸭" price="290" :intro="str"></Product>
</div>
</template>
<script>
// 1. 创建组件 (.vue文件)
// 2. 引入组件
import Product from './components/MyProduct'
export default {
data() {
return {
str: "好贵啊, 快来啊, 好吃"
}
},
// 3. 注册组件
components: {
// Product: Product // key和value变量名同名 - 简写
Product
}
}
</script>
<style></style>
demo\src\components\MyProduct.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'intro']
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
什么时候需要父传子技术? 从一个vue组件里把值传给另一个vue组件(父->子)
父传子口诀(步骤)是什么?
子组件内, props定义变量, 在子组件使用变量
父组件内, 使用子组件, 属性方式给props变量传值
2.组件通信_父向子-配合循环
目标:父组件 -> 子组件 循环使用-传值
每次循环obj和组件都是独立的, 新的
App.vue
<template>
<div>
<MyProduct v-for="obj in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info">
</MyProduct>
</div>
</template>
<script>
// 目标: 循环使用组件-分别传入数据
// 1. 创建组件
// 2. 引入组件
import MyProduct from './components/MyProduct.vue'
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
},
{
id: 3,
proname: "超级无敌的冰激凌",
proprice: 14.2,
info: "炎热的夏天, 来个冰激凌了",
},
],
};
},
// 3. 注册组件
components: {
// MyProduct: MyProduct
MyProduct
}
};
</script>
<style></style>
demo\src\components\MyProduct.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
</div>
</template>
<script>
export default {
props: ['title', 'price', 'intro']
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
循环使用组件注意事项? 每次循环, 变量和组件, 都是独立的
3.单向数据流
目标:从父到子的数据流向, 叫单向数据流
原因: 子组件修改, 不通知父级, 造成数据不一致性
Vue规定props里的变量, 本身是只读的
为何不建议, 子组件修改父组件传过来的值?
父子数据不一致, 而且子组件是依赖父传入的值
什么是单向数据流? 从父到子的数据流向, 叫单向数据流
props里定义的变量能修改吗?不能, props里的变量本身是只读的
4.组件通信_子向父_自定义事件
目标:子组件触发父自定义事件方法
- 需求: 商品组件, 实现砍价功能
- 前置补充, 父 -> 索引 -> 子组件 (用于区分哪个子组件)
父组件内, 绑定自定义事件和事件处理函数
语法: @自定义事件名="父methods里函数名"
子组件内, 恰当的时机, 触发父给我绑的自定义事件, 导致父methods里事件处理函数执行
App.vue
<template>
<div>
<!-- 目标: 子传父 -->
<!-- 1. 父组件, @自定义事件名="父methods函数" -->
<MyProduct v-for="(obj, ind) in list" :key="obj.id" :title="obj.proname" :price="obj.proprice" :intro="obj.info"
:index="ind" @subprice="fn"></MyProduct>
</div>
</template>
<script>
import MyProduct from './components/MyProduct_sub.vue'
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
},
{
id: 3,
proname: "超级无敌的冰激凌",
proprice: 14.2,
info: "炎热的夏天, 来个冰激凌了",
},
],
};
},
components: {
MyProduct
},
methods: {
fn(inde, price) {
// 逻辑代码
this.list[inde].proprice > 1 && (this.list[inde].proprice = (this.list[inde].proprice - price).toFixed(2))
}
}
};
</script>
<style></style>
demo\src\components\MyProduct_sub.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="subFn">宝刀-砍1元</button>
</div>
</template>
<script>
import eventBus from '../EventBus'
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn() {
this.$emit('subprice', this.index, 1) // 子向父
eventBus.$emit("send", this.index, 1) // 跨组件
}
}
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()
什么时候使用子传父技术?
当子想要去改变父里的数据
子传父如何实现? 父组件内, 给组件@自定义事件="父methods函数" 子组件内, 恰当时机this.$emit('自定义事件名', 值)
总结:
组件是什么?
是一个vue实例, 封装标签, 样式和JS代码
组件好处? 便于复用, 易于扩展
组件通信哪几种, 具体如何实现? 父 -> 子 父
5.组件通信-EventBus
目标:App.vue里引入MyProduct.vue和List.vue
目标:常用于跨组件通信时使用
⚫ 语法
- src/EventBus/index.js – 创建空白Vue对象并导出
- 在要接收值的组件(List.vue) eventBus.$on('事件名', 函数体)
- 要传递值的组件(MyProduct.vue) eventBus.$emit('事件名', 值)
App.vue
<template>
<div style="overflow: hidden;">
<div style="float: left;">
<MyProduct
v-for="(obj, ind) in list"
:key="obj.id"
:title="obj.proname"
:price="obj.proprice"
:intro="obj.info"
:index="ind"
@subprice="fn"
></MyProduct>
</div>
<div style="float: left;">
<List :arr="list"></List>
</div>
</div>
</template>
<script>
import MyProduct from "./components/MyProduct_sub.vue";
import List from "./components/ProductList.vue";
export default {
data() {
return {
list: [
{
id: 1,
proname: "超级好吃的棒棒糖",
proprice: 18.8,
info: "开业大酬宾, 全场8折",
},
{
id: 2,
proname: "超级好吃的大鸡腿",
proprice: 34.2,
info: "好吃不腻, 快来买啊",
},
{
id: 3,
proname: "超级无敌的冰激凌",
proprice: 14.2,
info: "炎热的夏天, 来个冰激凌了",
},
],
};
},
components: {
MyProduct,
List,
},
methods: {
fn(inde, price) {
this.list[inde].proprice > 1 &&
(this.list[inde].proprice = (this.list[inde].proprice - price).toFixed(
2
));
},
},
};
</script>
<style>
</style>
components/ProductList.vue
<template>
<ul class="my-product">
<li v-for="(item, index) in productList" :key="index">
<span>{{ item.proname }}</span>
<span>{{ item.proprice }}</span>
</li>
</ul>
</template>
<script>
import eventBus from "../EventBus/index";
export default {
props: ["arr"],
data() {
return {
productList: []
};
},
created() {
this.productList = [...this.arr]; // 创建副本
eventBus.$on("send", (index, price) => {
this.handlePriceUpdate(index, price);
});
},
methods: {
handlePriceUpdate(index, price) {
if (this.productList[index].proprice > 1) {
// 更新副本而不是原始的 arr
this.productList[index].proprice = (this.productList[index].proprice - price).toFixed(2);
// 发送事件通知父组件更新
eventBus.$emit("priceUpdated", this.productList);
}
}
}
};
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
components/MyProduct_sub.vue
<template>
<div class="my-product">
<h3>标题: {{ title }}</h3>
<p>价格: {{ price }}元</p>
<p>{{ intro }}</p>
<button @click="subFn">宝刀-砍1元</button>
</div>
</template>
<script>
import eventBus from '../EventBus'
export default {
props: ['index', 'title', 'price', 'intro'],
methods: {
subFn() {
this.$emit('subprice', this.index, 1) // 子向父
eventBus.$emit("send", this.index, 1) // 跨组件
}
}
}
</script>
<style>
.my-product {
width: 400px;
padding: 20px;
border: 2px solid #000;
border-radius: 5px;
margin: 10px;
}
</style>
什么时候使用eventBus技术? 当2个没有引用关系的组件之间要通信传值
eventBus技术本质是什么? 空白Vue对象, 只负责$on和$emit