Day 05 Vue—组件
一、组件
1、组件是什么?有什么用
组件类似于django模板语言中的include,用来扩展HTML元素,封装可用代码,目的是复用
-------例如:有一个轮播,可以在很多页面中使用,一个轮播有js,css,html
-------组件把js,css,html放到一起,有逻辑,有样式,有html
2、组件注册方式
2.1 全局组件
定义方式是用Vue对象创建一个新的component
<script>
//没有代码提示,语法检查,目前这么用
//后面会使用webpack打包,直接定义成 xx.vue文件,通过webpack打包
Vue.component('global',{ // 全剧组件名 + 组件内容
template:`
<div>
<button @click="handleClick">返回</button>
我是NavBar
<button style="background: red">主页</button>
</div>
`,
methods:{
handleClick(){
console.log('nav nav')
}
}
})
var vm = new Vue({
el: '#box',
data: {
},
})
</script>
2.2 局部组件
局部组件是只能在局部使用的组件,在当前页面的Vue下的components中–放在 Vue实例(根组件) 中
<script>
let vm = new Vue({
el: '#box',
data: {},
// 创建1个组件对象(局部组件)
components: {
local: { // local 组件名
template: `
<div>
<div style="background: rgba(104,255,104,0.7); padding: 5px 10px; border-radius: 5px; margin: 3px 50px 3px 0;"
@click="handleClick">我是局部组件
</div>
</div>
`, // 组件的模板
methods: {
handleClick() {
console.log('我被点击了')
}
}
}
}
</script>
3、组件使用方式
组件的使用方式非常简单,创建一个同名的标签就好了
<div id="box" style="max-width: 300px">
<local></local>
<global></global>
</div>
注意点:
- 定义的组件(body中的位置)必须要放在Vue实例(这也是一个组件 根组件)中
- 局部组件 必须放在 全局组件/根组件 中,无法单独使用
二、组件编写方式和Vue实例的区别
1 自定义组件需要有一个root element,一般包裹在一个div中
2 父子组件的data是无法共享
3 组件可以有data,methods,computed…,但是data 必须是一个函数
1、Vue实例(其实,它也是1个组件,是1个根组件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
<title>Title</title>
</head>
<div id="box">
<ul>
<li>姓名:{{name}}</li>
<li>年龄:{{age}}</li>
<li><button @click="handleClick">Test Button</button></li>
</ul>
</div>
<body>
<script>
let vm = new Vue({
el:'#box',
data:{
name:'Super',
age:99
},
methods:{
handleClick(){
alert('按钮被点击!')
}
}
})
</script>
</body>
</html>
2、组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>局部组件</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box" style="max-width: 300px">
<ul>
<li v-for="i in 3">
<global></global>
</li>
</ul>
</div>
</body>
<script>
// 创建1个组件对象(全局组件)
Vue.component('global', {
template: `
<div>
<div style="background: rgba(255,104,104,0.7); padding: 5px 10px; border-radius: 5px;margin: 5px 0;">
我是全局组件
</div>
<local></local>
<br>
</div>
`,
// 创建1个组件对象(局部组件)
components: {
local: {
template: `
<div>
<div style="background: rgba(104,255,104,0.7); padding: 5px 10px; border-radius: 5px; margin: 3px 50px 3px 0;">我是局部组件</div>
</div>
`,
}
}
})
let vm = new Vue({
el: '#box',
})
</script>
</html>
3、区别
3.1 自定义组件需要有1个 root element
,一般包裹在 1个div
中
3.2 父子组件的data
是无法共享的
- 这一点就像Docker的容器一样,是相互隔离的
- 就算父子的data中数据相同,拥有相同的方法,也是互不影响的
3.3 组件可以有data、methods、computed…,但是 data
必须是一个函数
Vue实例:data是1个键值对,用来存放属性的
var vm = new Vue({
el: '#box',
data: {
isShow: true
}
})
组件:data是1个函数,需要有返回值(return
)
Vue.component('global', {
template: `
<div>
<div style="background: rgba(255,104,104,0.7); padding: 5px;" @click="handleClick">我是头部组件</div>
<div v-if="isShow">显示消失</div>
</div>
`,
methods: {
handleClick() {
console.log('我被点击了')
this.isShow = !this.isShow
}
},
data() {
return {
isShow: true
}
}
})
三、组件通信
介绍组件间的通信必须要介绍props
组件
props是组件中非常重要的一个选项。在 Vue 中,父子组件的关系可以总结为
props down
,events up
。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。
1、父传子
1.1 静态props
静态Prop通过为子组件在父组件中的占位符添加特性的方式来达到传值的目的
子组件要显式地用 props
选项声明它期待获得的数据
Vue.component("global", {
template: `
<div>
<div style="background: rgba(255,104,104,0.7); padding: 5px;">全局组件/子组件</div>
{{desname}} // 显式调用
{{desage}}
</div>
`,
props:['desname', 'desage']
});
1.2命名约定(我没有遵守)
- 对于props声明的属性来说,在父级HTML模板中,属性名需要使用中划线写法
- 子级props属性声明时,使用小驼峰或者中划线写法都可以;而子级模板使用从父级传来的变量时,需要使用对应的小驼峰写法
1.3 动态props
在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的HTML特性相类似,就是用 v-bind。每当父组件的数据变化时,该变化也会传导给子组件
因为v-bind='变量名'
所以我们传递的其实是父组件的变量
<div id="box">
<global desname="LeeGo" desage="18"></global>
<global :desname="name" desage="19"></global>
<global desname="WanGo" :desage="age"></global>
</div>
<body>
<script>
Vue.component("global", {
template: `
<div>
<div style="background: rgba(255,104,104,0.7); padding: 5px;">全局组件/子组件</div>
{{desname}}
{{desage}}
</div>
`,
props:['desname', 'desage']
});
let vm = new Vue({
el: "#box",
data: {
name: "Super",
age: 99
},
methods: {
handleClick() {
alert("按钮被点击!");
}
}
});
</script>
</body>
1.4 props验证
验证传入的 props 参数的数据规格,如果不符合数据规格,Vue 会发出警告。
能判断的所有种类(也就是 type 值)有:
String, Number, Boolean, Function, Object, Array, Symbol
2、子传父(通过事件方式)
点击子组件,就会触发父组件的某个函数执行
子组件向父组件传值,同样的他们之间的契合点也是子组件引入的地方。
首先来到子组件,用$emit发射数据
然后再来到父组件,在契合点的地方
子组件传参
方法一
子组件传出单个参数时:
1 // 子组件
handleNav() {
console.log('我是子组件的函数')
this.$emit('my_event', 666, 777, this.name)
}
3 // 父组件
handleClick(a,b,c) {
console.log('我是父组件的函数')
console.log(a)
console.log(b)
console.log(c)
}
方法二
子组件传出多个参数时:
1 子组件
handleNav() {
console.log('我是子组件的函数')
this.$emit('my_event', 666, 777, this.name)
}
3 父组件 arguments 是以数组的形式传入
methods: {
handleClick(arguments) {
console.log(arguments)
}
四、ref属性
1、父传子
ref放在标签上,拿到的是原生的DOM节点
ref放在组件上,拿到的是组件对象
通过这种方式实现子传父(this.$refs.mychild.text)
通过这种方式实现父传子(调用子组件方法传参数)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>子传父</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<input type="text" ref="myRef">
<button @click="handleButton">点我</button>
</div>
</body>
<script>
// 创建1个组件对象(全局组件/子组件)
Vue.component('global', {
template: `
<div>
<input type="text" v-model="myText">
</div>
`,
data() {
return {
myText: ''
}
},
methods: {
handleClick() {
this.$emit('my_event', this.myText)
this.$emit('my_event', this.innerHTML)
}
}
})
// 父组件
let vm = new Vue({
el: '#box',
data: {
name: ''
},
methods: {
handleShow(a) {
this.name = a
},
handleButton() {
console.log(this.$refs)
console.log(this.$refs.myRef)
console.log(this.$refs.myRef.value)
}
}
})
</script>
</html>
2、父传子 直接赋值
<html lang="en">
<head>
<meta charset="UTF-8">
<title>子传父</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<global @my_event="handleClick(arguments)" ref="global"></global>
</div>
</body>
<script>
// 创建1个组件对象(全局组件/子组件)
Vue.component("global", {
template: `
<div>
<div style="background: rgba(255,104,104,0.7); padding: 5px;">全局组件/子组件{{child_name}}</div>
<button @click="handleNav">点我</button>
</div>
`,
data() {
return {
child_name: ""
};
},
methods: {
handleNav() {
console.log("我是子组件的函数");
this.$emit("my_event", 666, 777, this.name);
}
}
});
// 父组件
let vm = new Vue({
el: "#box",
data: {
name: "xxxx"
},
methods: {
handleClick() {
this.$refs.global.child_name=this.name
console.log(this.$refs.global);
}
}
});
</script>
</html>
五、事件总线
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>子传父</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<global1></global1>
<hr>
<global2></global2>
</div>
</body>
<script>
// 定义1个时间总线
let bus = new Vue({})
// 组件1
Vue.component('global1', {
template: `
<div>
<h3>组件1</h3>
<input type="text" v-model="myText">
<button @click="handleClick1">点我传递数据到另一个组件</button>
</div>
`,
data() {
return {
myText: ''
}
},
methods: {
handleClick1() {
console.log(this.myText)
bus.$emit('any', this.myText) // 通过事件总线发送
}
}
})
// 组件2
Vue.component('global2', {
template: `
<div>
<h3>组件2</h3>
收到的消息是:{{recvText}}
</div>
`,
data() {
return {
recvText: ''
}
},
mounted() { // 组件的挂载(生命周期钩子函数中的1个),开始监听时间总线上的:any
bus.$on('any', (item) => {
console.log('收到了', item,)
this.recvText = item
})
},
methods: {}
})
// 父组件
let vm = new Vue({
el: '#box',
data: {},
})
</script>
</html>
HTML
复制
[](https://gitee.com/xuexianqi/img/raw/master/img/13 bus.gif)
四:动态组件
1.基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>动态组件</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<ul>
<li>
<button @click="who='child1'">首页</button>
</li>
<li>
<button @click="who='child2'">订单</button>
</li>
<li>
<button @click="who='child3'">商品</button>
</li>
</ul>
<component :is="who"></component>
</div>
</body>
<script>
let vm = new Vue({
el: '#box',
data: {
who: 'child1'
},
components: {
child1: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(255,104,104,0.7)">我是首页</span>
</div>
`,
},
child2: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(255,104,255,0.7)">我是订单</span>
</div>
`,
},
child3: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(104,255,104,0.7)">我是商品</span>
</div>
`,
}
}
})
</script>
</html>
HTML
复制
[](https://gitee.com/xuexianqi/img/raw/master/img/16 dynamic01.gif)
2.keep-alive的使用
keep-alive
可以让输入框内有的内容一致保持,不会因为切换而重置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="box">
<ul>
<li>
<button @click="who='child1'">首页</button>
</li>
<li>
<button @click="who='child2'">订单</button>
</li>
<li>
<button @click="who='child3'">商品</button>
</li>
</ul>
<keep-alive>
<component :is="who"></component>
</keep-alive>
</div>
</body>
<script>
let vm = new Vue({
el: '#box',
data: {
who: 'child1'
},
components: {
child1: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(255,104,104,0.7)">我是首页</span>
<input type="text">
</div>
`,
},
child2: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(255,104,255,0.7)">我是订单</span>
<input type="text">
</div>
`,
},
child3: {
template: `
<div>
<span style="border-bottom: 5px solid rgba(104,255,104,0.7)">我是商品</span>
<input type="text">
</div>
`,
}
}
})
</script>
</html>
HTML
复制
[](https://gitee.com/xuexianqi/img/raw/master/img/17 dynamic02.gif)
五:slot 插槽
1.基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot 插槽</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<child>
<h6>Hello World</h6>
</child>
</div>
</body>
<script>
let vm = new Vue({
el: '#box',
data: {
who: 'child1'
},
components: {
child: {
template: `
<div>
<slot></slot>
<span style="border-bottom: 5px solid rgba(255,104,104,0.7)">我是组件的原内容</span>
<slot></slot>
</div>
`,
},
}
})
</script>
</html>
HTML
复制
2.小案例(通过插槽实现在1个组件中控制另1个组件的显示隐藏)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>slot 插槽</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<!--通过插槽实现在一个组件中控制另一个组件的显示隐藏-->
<child1>
<button @click="isShow=!isShow">显示/隐藏组件2</button>
</child1>
<child2 v-if="isShow"></child2>
</div>
</body>
<script>
Vue.component('child1', {
template: `<div>
组件1
<slot></slot>
</div>`,
})
Vue.component('child2', {
template: `<div>
<h3>组件2</h3>
</div>`,
})
var vm = new Vue({
el: '#box',
data: {
isShow: true
}
})
</script>
</html>
HTML
复制
[](https://gitee.com/xuexianqi/img/raw/master/img/15 slot1.gif)
3.具名插槽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>具名插槽</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
<!-- 具名插槽,把p标签给a插槽,div标签给b插槽-->
<child>
<p slot="a">我是具名插槽a插入的内容</p>
<div slot="b">我是具名插槽b插入的内容</div>
</child>
</div>
</body>
<script>
Vue.component('child', {
template: `<div>
<slot name="a"></slot>
<hr>
<span style="border-bottom: 5px solid rgba(255,104,104,0.7)">我是组件的原内容</span>
<hr>
<slot name="b"></slot>
</div>`,
})
var vm = new Vue({
el: '#box',
data: {}
})
</script>
</html>
HTML
复制
可以指定标签放在某个插槽的位置