vue学习—基于vue2中的组件
此篇是基于 vue2.x
的开发环境
1.组件
1.1注册组件
在项目文件夹下的src文件夹下的components文件夹下创建的一个.vue文件就是一个组件,组件的script中使用this,这个this代表当前组件的实例
局部注册
先通过import 组件名 from ‘组件路径’ 导入组件;在export default 中用components注册组件
示例代码:
写在App.vue文件中
<template>
<!-- 一个.vue文件就是一个组件, 一个项目中只有一个根组件 App.vue -->
<!-- 一个.vue文件内部一般是由三部分组成: template , script , style -->
<!-- template 内部写标签 , 只能有一个根元素 -->
<!-- script 内部写js逻辑 , 内部需要 export default {}, 所有的js代码都需要写在这里 -->
<!-- style 内部写css样式 , 可以通过lang='xx'指定style内部的css语法 , 可以通过设置 scoped 属性 让多个.vue文件之间的样式互不影响 -->
<div id="app">
<!-- 搜索框 -->
<Search></Search>
<!-- 轮播图 -->
<Swiper></Swiper>
<!-- 列表 -->
<List></List>
<!-- Tabbar -->
<Tabbar></Tabbar>
</div>
</template>
<script>
//导入组件
import Search from './components/Search.vue'
import Swiper from './components/Swiper.vue'
import List from './components/List.vue'
import Tabbar from './components/Tabbar.vue'
export default {
components:{ //负责注册组件( 局部组件 , 只能在当前注册的文件中使用 )
// Search,
Swiper,
List,
Tabbar
}
}
</script>
<style lang="scss">
// 这里不加scoped , 因为 这里一般写全局样式
*{
margin: 0;
padding: 0;
}
</style>
全局注册
依旧是先导入组件,再注册组件通过vue.component(’ 组件名 ’ , 导入组件名)
示例代码:
在main.js文件中
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 导入Search组件
import Search from './components/Search.vue'
// 注册组件(全局组件,可以在所有的.vue文件中使用)
Vue.component('Search', Search);
//实例化Vue对象
new Vue({
render: h => h(App),
}).$mount('#app')
组件的使用
使用组件(可以是全局组件 或 局部组件)
在在App.vue文件中使用:<组件名/> 或者 <组件名><组件名/>
1.2关于模块(一个模块就是一个js文件)的导入、导出
在项目文件夹下的src文件夹下创建一个文件utils放置创建的js文件
第一种:默认导出
导出:export default 要导出的变量名
导入:import 导出的变量名 from ‘路径’
示例代码:
新建的js文件导出:
//定义函数
function add(a,b){
return a+b;
}
//默认抛出(导出)
export default add;
main.js文件导入:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 默认导入 可以给默认导入的数据改名
// 默认导出和默认导入 配合使用
import abc from './utils/a'
console.log( abc(10,20) );
//实例化Vue对象
new Vue({
render: h => h(App),
}).$mount('#app')
第二种:命名导出
导出:export { 变量名1,变量名2 ,…}
导入:import {变量名1,变量名2,…} from ‘路径’
示例代码:
js文件导出:
function sub(a,b){
return a-b;
}
function mul(a,b){
return a*b;
}
//命名导出
export {
sub,
mul
};
main.js文件导入
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 命名导入
// 命名导入导出是对应的配合使用的,命名变量名字要相同
// {sub,mul}解构赋值,解析出你想要导入的文件
// 命名导入,必须以{}的形式导入解构时不能改名字要和导出中的名字一样
import {sub,mul} from './utils/b';
console.log(sub(10,20),mul(10,20));
console.log(abc(10,20));
//实例化Vue对象
new Vue({
render: h => h(App),
}).$mount('#app')
1.3组件生命周期钩子
生命周期钩子是在Vue组件实例生命周期的某个阶段执行的已定义方法。从初始化开始到它被销毁,实例对象都会遵循不同的生命阶段。这是一个着名的图表,表示钩子函数执行顺序。
- beforeCreate:在实例初始化之后创建完成之前,数据观测(data observer)和 event/watcher事件配置之前被调用
- created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, event/watch事件回调。然而,挂载阶段还没有开始,$el属性目前不可见。
- beforeMount:挂载开始之前被调用:相关的render函数首次被调用。
- mounted:el被新创建的
vm.$el
替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el
也在文档内。 - beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。这里适合在更新之前访问现有的DOM,比如手动移出已添加的事件监听器。
- updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue实例销毁后调用哪个。调用后,vue实例指示的所有东西都会解绑定,所有的事件监听器会被移出,所有的子实例也会被销毁。
注意:
- 以上这些函数都是组件的什么周期函数,它们在组件生命过程中会在特定时刻自动调用。
- 实例化期里的方法只会执行以此份
- 挂载期里的方法只会执行一次;对于哪些组件挂载完成前必须执行而且只能执行一次的代码可以写在 mounted 函数中,在这里可以发送ajax请求,创建定时器,事件监听
- 更新期里的两个方法会随着组件更新反复执行
- 销毁期只会执行一次;如果 mounted 生命周期函数中做了发送ajax请求,创建定时器,事件监听(滚动条事假,键盘事件,绑定点击事件)等操作,这些操作会占用计算机内存,在组件销毁前如果没有及时释放这些内存,就会导致内存泄漏。
- $destroy():销毁组件的方法,组件销毁不一定是组件消失只是组件不能用了。
示例代码:
App.vue文件
<template>
<!-- 一个.vue文件就是一个组件, 一个项目中只有一个根组件 App.vue -->
<!-- 一个.vue文件内部一般是由三部分组成: template , script , style -->
<!-- template 内部写标签 , 只能有一个根元素 -->
<!-- script 内部写js逻辑 , 内部需要 export default {}, 所有的js代码都需要写在这里 -->
<!-- style 内部写css样式 , 可以通过lang='xx'指定style内部的css语法 , 可以通过设置 scoped 属性 让多个.vue文件之间的样式互不影响 -->
<div id="app">
<h1>count:{{count}}</h1>
<button @click="$destroy()">销毁当前组件</button>
</div>
</template>
<script>
export default {
data(){
return {
count:0,
timerid:null
}
},
computed:{
},
// 以下这些函数都是 组件的生命周期函数,它们在组件生命过程中会在特定时刻自动调用。
// 实例化期,这里的方法只会执行一次
beforeCreate(){
console.log('组件-创建(实例化)前');
},
created(){
console.log('组件-创建(实例化)完成');
},
// 挂载期,这里的方法只会执行一次
beforeMount(){
console.log('组件-挂载前');
},
mounted(){
// 对于哪些在组件挂载完成前 必须执行而且只能执行一次的代码可以写在这里
console.log('组件-挂载完成');
// 在这里可以发送ajax请求,创建定时器,事件监听
this.timerid = setInterval(()=>{
this.count++;
},1000)
},
// 更新期,这里的两个方法会随着组件更新 反复执行
beforeUpdate(){
console.log('组件-更新前');
},
updated(){
console.log('组件-更新完成');
},
// 销毁期,只会执行一次
beforeDestroy(){
console.log('组件-销毁前');
// 如果在 mounted 生命周期函数中做了 发送ajax请求,创建定时器,事件监听 等操作,这些操作会占用计算机内存,在组件销毁前如果没有及时释放这些内存,就会导致内存泄漏。
clearInterval(this.timerid);
},
destroyed(){
console.log('组件-销毁完成');
}
}
</script>
<style lang="scss" scoped>
</style>
2.组件通信
所谓组件通信,即组件和组件之间的数据传递,包含两个方面:
Ø 具有嵌套关系的父子组件之间数据的传递问题;
Ø 兄弟组件之间数据的传递问题;
具体可以分为:
- 父组件向子组件传值
- 子组件向父组件传值
- 跨级组件传值
- 兄弟组件之间传值
2.1父组件向子组件传值
当数据存在父组件中,需要将父组件中的数据传到子组件中
父组件在子组件身上通过自定义属性传值,子组件内部通过props接收值
父组件:
<div id="app">
<div class="tabs">
<!-- App.vue与渲染的组件之间是父子关系 -->
<!-- 所有数据都存在于父组件中,需要将父组件中的数据传到子组件中进行渲染 -->
<!-- 父组件向子组件传值:父组件在子组件身上通过自定义属性传值,子组件内部通过props接收值 -->
<!-- 子组件向父组件传值:父组件提前在子组件身上绑定自定义事件,子组件内部通过$emit触发 -->
<!-- 渲染左侧分类 -->
<TabLeft :tabs="tabs" ></TabLeft>
<!-- 渲染右侧分类 -->
<TabRight ></TabRight>
</div>
</div>
export default {
data() {
return {
// 用来保存当前点击的分类下标
currentIndex: 0,
tabs: [
{
tabName: "三文鱼",
list: [
{ pic: pic1, name: "三文鱼", price: 45 },
{ pic: pic1, name: "高级三文鱼", price: 55 },
{ pic: pic1, name: "vip三文鱼", price: 65 },
]
},
{
tabName: "刺身",
list: [
{ pic: pic2, name: "刺身", price: 45 },
{ pic: pic2, name: "高级刺身", price: 55 },
{ pic: pic2, name: "vip刺身", price: 65 },
]
},
{
tabName: "大虾",
list: [
{ pic: pic3, name: "大虾", price: 45 },
{ pic: pic3, name: "高级大虾", price: 55 },
{ pic: pic3, name: "vip大虾", price: 65 },
]
},
]
};
},
components: {
TabRight,
TabLeft,
}
子组件:
export default {
// 通过props接收从父组件传来的值,该值是只读的(不能修改)
// props:['tabs','currentIndex']//数组形式
props:{//对象形式,可以对传来的数据进行类型验证
tabs:{type:Array, default:[]},
}
}
2.2子组件向父组件传值
父组件提前在子组件身上绑定自定义事件,子组件内部通过$emit触发
on订阅,emit发布,on和emit是在Vue的原型上的,每个实例都可以调用。
父组件:
<div id="app">
<div class="tabs">
<!-- App.vue与渲染的组件之间是父子关系 -->
<!-- 所有数据都存在于父组件中,需要将父组件中的数据传到子组件中进行渲染 -->
<!-- 父组件向子组件传值:父组件在子组件身上通过自定义属性传值,子组件内部通过props接收值 -->
<!-- 子组件向父组件传值:父组件提前在子组件身上绑定自定义事件,子组件内部通过$emit触发 -->
<!-- 渲染左侧分类 -->
<!-- <TabLeft :tabs="tabs" :currentIndex="currentIndex" @deliverIndex="changelist" ></TabLeft> -->
<TabLeft :tabs="tabs" :currentIndex="currentIndex" @deliverIndex="changelist" ></TabLeft>
<!-- 渲染右侧分类 -->
<TabRight :tabs="tabs" :currentIndex="currentIndex"></TabRight>
</div>
</div>
export default {
data() {
return {
// 用来保存当前点击的分类下标
currentIndex: 0,
tabs: [
{
tabName: "三文鱼",
list: [
{ pic: pic1, name: "三文鱼", price: 45 },
{ pic: pic1, name: "高级三文鱼", price: 55 },
{ pic: pic1, name: "vip三文鱼", price: 65 },
]
},
{
tabName: "刺身",
list: [
{ pic: pic2, name: "刺身", price: 45 },
{ pic: pic2, name: "高级刺身", price: 55 },
{ pic: pic2, name: "vip刺身", price: 65 },
]
},
{
tabName: "大虾",
list: [
{ pic: pic3, name: "大虾", price: 45 },
{ pic: pic3, name: "高级大虾", price: 55 },
{ pic: pic3, name: "vip大虾", price: 65 },
]
},
]
};
},
components: {
TabRight,
TabLeft,
},
methods:{
changelist(index){
this.currentIndex = index;
}
}
子组件:
<div class="tab" :class="{active:currentIndex ==index}" @click="$emit('deliverIndex',index)" v-for="(item,index) in tabs" :key="index">{{item.tabName}}</div>
<div class="tabright">
<!-- 渲染多个商品(列表) -->
<div class="good" v-for="(item,index) in tabs[currentIndex].list" :key="index">
<div class="imgbox">
<img :src="item.pic" alt="">
</div>
<div class="text">
<div class="name">{{item.name}}</div>
<div class="price">¥{{item.price}}</div>
</div>
</div>
</div>
**注意:**emit中传入的事件名称必须全部小写
2.3兄弟组件之间的通信
需要借助一个公共的eventbus (通信管道) 其中一个组件提前绑定事件,另外一个兄弟组件去触发事件并传值
创建一个空实例,例如:vm = new Vue({});作为通信桥梁;
在需要接收数据的组件中用vm.$on()监听自定义事件(一般在mounted函数中处理),并在回调函数中处理传递过来的参数(注意回调函数中的this处理);
在需要传值的组件中用vm.$emit()触发自定义事件,并传递数据;
实例化Vue空实例:
var vm = new Vue({});
步骤:
1.先在src中创建一个utils文件夹在该文件夹中创建一个tools.js文件,在该文件中实例化Vue空实例,并导出
import Vue from 'vue'
// 实例化Vue 并导出
export default new Vue()
2.在需要接收数据的组件中用vm.$on()监听自定义事件(一般在mounted函数中处理)
<div class="list">
<!-- 渲染多个商品(列表) -->
<div class="good" v-for="(item,index) in tabs[currentIndex].list" :key="index">
<div class="imgbox">
<img :src="item.pic" alt="">
</div>
<div class="text">
<div class="name">{{item.name}}</div>
<div class="price">¥{{item.price}}</div>
</div>
</div>
</div>
import eventbus from '../utils/tools'
export default {
// 通过props接收从父组件传来的值,该值是只读的(不能修改)
data(){
return {
currentIndex:0
}
},
props:['tabs'],
mounted(){
// 绑定事件
eventbus.$on('deliverIndex',(index=>{
this.currentIndex = index; //保存接收到的值
}))
}
}
3.在需要传值的组件中用vm.$emit()触发自定义事件,并传递数据;
触发事件并不一定要传值有可能只是触发这个事件
兄弟组件a:
<div class="tabbox">
<!-- 渲染多个分类按钮 -->
<div class="tab" :class="{active: currentIndex==index }" @click="handleClick(index)" v-for="(item,index) in tabs" :key="index">{{item.tabName}}</div>
</div>
// 导入空实例
import eventbus from '../utils/tools'
export default {
// 通过props接收从父组件传来的值,该值是只读的(不能修改)
// props:['tabs','currentIndex'] //数组形式
data(){
return {
currentIndex:0
}
},
props:{//对象形式,可以对传来的数据进行类型验证
tabs:{type:Array,default:[]}
},
methods:{
handleClick(index){
eventbus.$emit('deliverIndex',index);//触发事件并传值
this.currentIndex = index;
}
}
}
2.4跨组件传值
外层组件通过provide传值, 内层组件通过inject接收值。
示例:
将外层组件中的值传给Child1组件再将值通过Child1组件传给Child2组件中再将值通过Child2组件传给Child3组件中
示例代码:
App.vue文件
<template>
<!-- 一个.vue文件就是一个组件, 一个项目中只有一个根组件 App.vue -->
<!-- 一个.vue文件内部一般是由三部分组成: template , script , style -->
<!-- template 内部写标签 , 只能有一个根元素 -->
<!-- script 内部写js逻辑 , 内部需要 export default {}, 所有的js代码都需要写在这里 -->
<!-- style 内部写css样式 , 可以通过lang='xx'指定style内部的css语法 , 可以通过设置 scoped 属性 让多个.vue文件之间的样式互不影响 -->
<div id="app">
<Child1></Child1>
</div>
</template>
<script>
import Child1 from './components/Child1.vue';
export default {
components:{
Child1
},
// 跨组件传值:外层组件通过provide传值,内层组件通过inject接收值
provide(){//负责传值给内层组件
return {
money:8888,
list:[1,2,3]
}
}
}
</script>
<style lang="scss" scoped>
</style>
Child1.vue文件
<template>
<div class="child1">
<h1>我是子组件1 - {{money}}</h1>
<Child2/>
</div>
</template>
<script>
import Child2 from './Child2.vue'
export default {
components:{
Child2
},
inject:['money']
}
</script>
<style>
</style>
Child2.vue文件
<template>
<div class="child2">
<h1>我是子组件2 - {{money}}</h1>
<Child3 />
</div>
</template>
<script>
import Child3 from './Child3.vue'
export default {
components:{
Child3
},
inject:['money']
}
</script>
<style>
</style>
Child3.vue文件
<template>
<div class="child3">
<h1>我是子组件3 - {{money}} - {{list}} </h1>
</div>
</template>
<script>
export default {
//inject 接收从外层组件传来的值
inject:['money','list']
}
</script>
<style>
</style>
3.ref
获取dom节点:
通过ref属性给标签设置标识(别名),在js代码中通过this.$refs.xx 获取标识对应的dom节点
ref应用在组件身上
在js代码中通过this.$refs.xx获取标识对应的组件实例
示例代码:
<template>
<div id="app">
<!-- 通过ref属性给标签设置标识( 别名 ) -->
<!-- 在js代码中 通过 this.$refs.xx 获取标识对应的dom节点 -->
<input type="text" ref="phone">
<h1 ref="title">学习ref</h1>
<!-- ref应用在组件身上 -->
<!-- 在js代码中 通过 this.$refs.xx 获取标识对应的组件实例 -->
<Child ref="ch" />
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
components:{
Child
},
mounted(){
console.log( this.$refs.phone );
console.log( this.$refs.title );
console.log( this.$refs.ch );
//调用子组件的add,sub方法
this.$refs.ch.add();
this.$refs.ch.sub();
}
}
</script>
<style lang="scss" scoped>
</style>
关于ref的示例
购物车组件
<template>
<div class="shopcart" v-show="visible" @click="changeVisible">
<!-- @click.事件修饰符 -->
<!-- @click.stop 阻止事件冒泡 -->
<!-- @click.ctrl 只有同时按下ctrl键并点击才会触发 -->
<!-- @click.right 只有按下鼠标右键才会触发 -->
<!-- @click.native 给子组件绑定原生事件,只有添加active修饰符才会触发 -->
<div class="content" @click.stop="handleClick">
<div class="title">
<span>已选商品</span>
<span @click="clean">清空</span>
</div>
<div v-if="list.length != 0" class="list">
<div v-for="(item,index) in list" :key="index" class="good">
<div class="imgbox">
<img :src="item.pic" alt="">
</div>
<div class="text">
<div class="name">{{item.name}}</div>
<div class="price">
<div>¥{{item.price}}</div>
<div>
<span @click="sub(item)">-</span>
<span>{{item.count}}</span>
<span @click="add(item)">+</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty" >购物车空空如也,去逛逛!</div>
</div>
</div>
</template>
<script>
var pic = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202102%2F26%2F20210226073347_50f94.thumb.1000_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1668131440&t=6df291a148cdb21289e91211d4daea32';
export default {
data(){
return {
visible:false,
list:[
{ name:'秋刀鱼',price:88,pic,count:1 },
{ name:'大黄鱼',price:777,pic,count:2 },
{ name:'皮皮虾',price:9,pic,count:3 },
]
}
},
methods:{
/* show(){
this.visible = true;//让购物车显示
},
hide(){
this.visible = false;//让购物车隐藏
}, */
changeVisible(){
this.visible = !this.visible;//让购物车显示/隐藏
},
clean(){
},
handleClick(){
console.log('触发了!');
}
}
}
</script>
<style lang='scss' scoped>
.shopcart{
height: 100%;
width: 100%;
position: fixed;
background-color: rgba(0, 0, 0, 0.3);
}
.content{
position: absolute;
bottom: 0;
background-color: white;
width: 100%;
padding: 10px;
box-sizing: border-box;
}
.content .title{
display: flex;
justify-content: space-between;
font-weight: bold;
}
.content .list .good{
display: flex;
margin: 10px 0;
}
.content .list .good .imgbox{
width: 80px;
margin-right: 10px;
}
.content .list .good .imgbox img{
width: 100%;
}
.content .list .good .text{
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.content .list .good .text .name{
font-weight: bold;
}
.content .list .good .text .price{
display: flex;
justify-content: space-between;
}
.content .list .good .text .price span{
display: inline-block;
width: 14px;
height: 14px;
text-align: center;
line-height: 14px;
background-color: lightskyblue;
color: white;
border-radius: 50%;
}
.content .list .good .text .price span:nth-child(2){
color: black;
background-color: white;
}
.empty{
text-align: center;
padding: 50px;
}
</style>
父组件
<template>
<!-- 一个.vue文件就是一个组件, 一个项目中只有一个根组件 App.vue -->
<!-- 一个.vue文件内部一般是由三部分组成: template , script , style -->
<!-- template 内部写标签 , 只能有一个根元素 -->
<!-- script 内部写js逻辑 , 内部需要 export default {}, 所有的js代码都需要写在这里 -->
<!-- style 内部写css样式 , 可以通过lang='xx'指定style内部的css语法 , 可以通过设置 scoped 属性 让多个.vue文件之间的样式互不影响 -->
<div id="app">
<Shopcart ref="shopcart"></Shopcart>
<div class="summary">
<!-- 通过ref设置的标识 获取子组件实例,进而调用子组件的方法 -->
<span @click="$refs.shopcart.changeVisible()">购物车</span>
<span>去结算</span>
</div>
<Child @click.native="handleChildClick"/>
</div>
</template>
<script>
import Shopcart from './components/Shopcart.vue'
import Child from './components/Child.vue'
export default {
components: { Shopcart,Child },
methods:{
handleChildClick(){
console.log('点击了Child组件');
}
}
}
</script>
<style lang="scss">
*{
margin: 0;
padding: 0;;
}
.summary{
position: fixed;
bottom: 0;
width: 100%;
background-color: #555;
color: white;
display: flex;
justify-content: space-between;
padding: 10px 10px;
box-sizing: border-box;
}
</style>
@click.事件修饰符
@click.left:只有点击鼠标左键时候才会触发
@click.right:只有点击右键才会触发
@click.middle:必须点击鼠标滚轮才会触发点击事件
@click.prevent:阻止默认行为,例如:a标签的跳转
@click.ctrl:点击事件想要触发必须按Ctrl键
@click.shift:当按下shift键时才会触发
@click.native:把点击事件绑到子组件上时原生点击事件
自定义事件:子组件在父组件中使用时,直接绑定在子组件上的事件就是自定义事件,必须经过子组件的触发vm.$emit()才能执行 原生事件:直接在子组件里的模板上绑定的事件,子组件引入后是可以直接触发的