Vue基础 - Day04
1. Vue组件 - component
什么是组件:
组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。
组件化和模块化的不同:
模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一
组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用
一个优秀的组件 / 模块应该做到:高内聚,低耦合
耦合性:每个模块之间相互联系的紧密程度,模块之间联系越紧密,则耦合性越高,模块的独立性就越差
内聚性:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即‘高内聚’
高内聚是说模块内部要高度聚合,低耦合是说模块与模块之间的耦合度要尽量低。 前者说的是模块内部的关系,后者说的是模块与模块之间的关系。 看似不同,实又相关。
如一个项目中有15个模块,需要在另一个项目中调用其中的一个模块,但是必须把全部的15个模块都移植过去才能使用,这就是高耦合,只移入所需的一个模块就可以使用,就是低耦合。
然后在一个模块中,各个元素(语句、程序段等)之间的联系程度越高,则内聚性越高,即高内聚。
定义全局组件
1.使用 Vue.extend 配合 Vue.component 方法
var login = Vue.extend({
template: '<h1>登录</h1>'
});
//第一个为组件名称,第二个为绑定的组件的名称
Vue.component('login', login);
2.直接使用 Vue.component 方法
Vue.component('register', {
template: '<h1>注册</h1>'
});
3.将模板字符串,定义到script标签中,同时,需要使用 Vue.component 来定义组件
<script id="tmpl" type="x-template">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</script>
<script>
Vue.component('account', {
template: '#tmpl'
});
</script>
4.将模板字符串,定义到template标签中
< template id="tmpl">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</ template >
同时,需要使用 Vue.component 来定义组件
Vue.component('account', {
template: '#tmpl'
});
一般只用第四种方式定义全局组件。
注意:
1.组件中的DOM结构,有且只能有唯一的根元素(Root Element)来进行包裹,
即最外层不能有同级的HTML标签,一般用一个 div 将其他的DOM元素包裹起来。
2.命名不能和 H5 中已有的标签重名,且驼峰命名不好使,因为HTML大小写不敏感,
可以用-连接
定义私有组件
在Vue实例中添加 components 属性即可:
const vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {
},
components: {
"register": {
template: "#register"
},
"test": {
template: "#login"
},
// 必须写ID,不可以写类名
// "lll": {
// template: ".lllllll"
// }
}
})
引用组件:
<div id='app'>
<register v-if="flag"></register>
<test v-if="flag"></test>
</div>
组件中数据和方法的调用
组件中也有data属性、methods属性、八个生命周期钩子函数等,但是data属性的属性值
由Vue实例中的对象变为了函数,并且函数中必须要有一个返回值,返回值里是组件可调用
的数据,如:
Vue.component("hello", {
template: "#hello",
data() {
return {
msg: "Hello World",
nums: 666
}
},
methods: {
add() {
this.nums += 6
}
}
})
其目的是为了组件复用时的数据隔离, 否则不同的组件都共用一个数据的话,一个发生变化每一个都会发生变化,一般情况下这不是我们想要的效果。
案例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.js"></script>
</head>
<body>
<div id='app'>
<button @click="flag=!flag">销毁Hello</button>
<hello v-if="flag"></hello>
</div>
<template id="hello">
<div>{{msg}}
<button @click="add">增6👉</button>
<span>{{nums}}</span>
</div>
</template>
</body>
<script>
Vue.component("hello", {
template: "#hello",
// 组件之间数据隔离
data() {
return {
msg: "Hello World",
nums: 666
}
},
methods: {
add() {
this.nums += 6
}
},
beforeCreate() {
console.log("创造哈喽前");
},
created() {
console.log("创造哈喽后,可以调用数据了");
},
beforeDestroy() {
console.log("销毁哈喽前");
},
destroyed() {
console.log("销毁哈喽后");
}
})
const vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {
},
})
</script>
</html>
使用 <component>
标签来引用组件,并通过is
属性来指定要加载的组件,is
属性也可进行数据绑定
//指定引用的组件为'hello'
<component is="hello"></component>
//加上冒号后,引号里的内容就变成了变量引用,需要注意单/双引号的添加
<component :is="flag?'hello':'nohello'"></component>
组件动画
<transition>
标签中有一个 mode
属性,来设置组件的动画是先出后入还是先入后出,先出后入为 mode="out-in"
,先入后出为 mode="in-out"
,通常先出后入的情况比较符合审美。
---------------------------------------
<p>先出后入的动画:</p>
<transition mode="out-in">
<component :is="flag?'hello':'nohello'"></component>
</transition>
---------------------------------------
<p>先入后出的动画:</p>
<transition mode="in-out">
<component :is="flag?'hello':'nohello'"></component>
</transition>
完整案例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.js"></script>
</head>
<style>
.v-enter,
.v-leave-to {
transform: translateX(200px);
}
.v-enter-active,
.v-leave-active {
transition: all 1s;
}
.v-enter-to,
.v-leave {
transform: translateX(0px);
}
</style>
<body>
<div id='app'>
<button @click="flag=!flag">销毁Hello</button>
<hello v-if="flag"></hello>
<nohello v-else></nohello>
-------------------------------------
<component is="hello"></component>
---------------------------------------
<!-- 注意是变量还是字符串 -->
<component :is="flag?'hello':'nohello'"></component>
---------------------------------------
<p>先出后入的动画:</p>
<transition mode="out-in">
<component :is="flag?'hello':'nohello'"></component>
</transition>
---------------------------------------
<p>先入后出的动画:</p>
<transition mode="in-out">
<component :is="flag?'hello':'nohello'"></component>
</transition>
</div>
<template id="hello">
<div>{{msg}}
<button @click="add">增6👉</button>
<span>{{nums}}</span>
</div>
</template>
<template id="nohello">
<div>
ByeByeWorld
</div>
</template>
</body>
<script>
Vue.component("hello", {
template: "#hello",
// 组件之间数据隔离
data() {
return {
msg: "Hello World",
nums: 666
}
},
methods: {
add() {
this.nums += 6
}
},
})
Vue.component("nohello", {
template: "#nohello"
})
const vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {
},
})
</script>
</html>
也可以选择不使用 <component>
标签,而是直接使用组件加 v-if
、v-else
,如:
<p>先出后入的动画:</p>
<transition mode="out-in">
<hello v-if="flag"></hello>
<nohello v-else></nohello>
</transition>
父组件传值子组件
1.子组件中设置 props属性
(和data同级),在props属性中设置接收数据的名称与类型,
可以使用数组设置多个接收类型,也可以使用 default函数
设置默认值;
Vue.component("course", {
template: "#course",
props: {
type: String,
'page-num': [Number, String],
'page-size': [Number, String],
test: {
type: String,
default: function () {
return '这是一个默认的名字'
}
},
},
data() {
return {
courseList: [],
baseUrl: "http://1.117.81.216:8086",
}
},
})
2.设置完成后在子组件标签中绑定props中定义的参数,属性值可以是父组件中的数据或者其他字符串,但是在标签中绑定参数时不能使用驼峰命名法,必须用kebaba-case命名法(即以-连接):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.0.0/dist/vue-router.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.js"></script>
</head>
<body>
<div id='app'>
<!-- 子组件标签中绑定props中定义的参数,属性值可以是父组件的数据 -->
<test :msgson="msgFather"></test>
</div>
<template id="test">
<div>
<p>{{msg}}------{{msgson}}</p>
</div>
</template>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
msgFather:"我是父组件的文字信息"
},
methods: {
},
components:{
"test":{
template:"#test",
data(){
return{
msg:"我是子组件的文字信息",
}
},
props:{
msgson:String
}
}
}
})
</script>
</html>
子组件传值父组件
子组件传值父组件需要我们在子组件标签中自定义传值事件,如:
<son @son-to-father="theFather" ref="myson"></son>
son-to-father
是在子组件的 methods 里写的方法,在方法中调用 this.$emit()
函数向父组件传值。
自定义事件不会自己调用,需要我们根据需求设置在什么情况下触发此事件去给父组件传值。
this.$emit() 的第一个参数是我们自己自定义的事件的名称,第二个参数是子组件给父组件传递的内容,可以是子组件 data 中的数据、一段字符串等等。
即:this.$emit(‘事件名称’,传递的数据)
theFather
是在父组件的 methods 里写的方法,触发sonToFather自定义事件后,会自动接收到子组件传递过来的数据,第一个参数就是接收到的数据,接收到后进行自己需要的操作即可。
代码举例:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id='app'>
<p>{{fatherMsg}}</p>
<button @click="changeMyson">改变子组件文字</button>
<son @son-to-father="theFather" ref="myson"></son>
</div>
<template id="son">
<div>
<p>{{msg}}</p>
<button @click="sonToFather">向父组件传送</button>
</div>
</template>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
fatherMsg: "默认的父组件文字"
},
methods: {
theFather(data) {
console.log(data);
this.fatherMsg = data
},
changeMyson() {
this.$refs.myson.msg = "改成父组件中的文字"
}
},
components: {
"son": {
template: "#son",
data() {
return {
msg: "这里是子组件中的文字",
}
},
methods: {
sonToFather() {
this.$emit("son-to-father", this.msg)
}
}
}
}
})
</script>
</html>
2. Vue插槽 - slot
插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。
插槽有 具名插槽 和 默认插槽 。
具名插槽就是给插槽取个名字,一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。
默认插槽就是没有名字,只有一个<slot></slot>
标签。
<!-- 课程组件 -->
<course page-size='10'>
<!-- <slot>默认插槽</slot> -->
<template v-slot:free>免费</template>
<!-- <template v-slot:disc>限时折扣</template> -->
</course>
<course type="boutique">
<template v-slot:free>精品</template>
</course>
<course type="discount">
<template v-slot:discount>限时折扣</template>
</course>
<template id="course">
<div>
<h2>
<!-- 默认插槽:<slot></slot> -->
<!-- 具名插槽:<slot name="free"></slot>课程 -->
<slot name="free"></slot>
<slot name="discount"></slot>课程
</h2>
<!-- <slot></slot> -->
<!-- <slot name="disc"></slot> -->
</div>
</template>