深入理解 Vue 组件
组件使用中的细节点
使用 is 属性,解决组件使用中的bug问题
1 <!DOCTYPE html>
2 <html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>组件使用中的细节点</title>
9 <script src="./vue.js"></script>
10 </head>
11
12 <body>
13 <div id="root">
14 <table>
15 <tbody>
16 <!-- H5编码规范要求,tbody内必须是tr,因此row组件不能用,会产生bug,
17 因此 is 关键字起到了很好的作用,将此时的 tr 标签等于我们创建的 row 子组件。
18 完美解决了既要使用组件永不会影响H5编码规范的问题
19 不仅仅是table标签,ul ol select 标签都有相同的问题。-->
20 <tr is="row"></tr>
21 <tr is="row"></tr>
22 <tr is="row"></tr>
23 </tbody>
24 </table>
25 </div>
26
27 <script>
28 // 创建全局子组件
29 Vue.component('row',{
30 template:"<tr><td>this is a row</td></tr>"
31 })
32
33 var vm = new Vue({
34 el:"#root",
35
36 })
37 </script>
38 </body>
39
40 </html>

子组件定义data数据,data必须是个函数
1 <!DOCTYPE html>
2 <html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>Document</title>
9 <script src="./vue.js"></script>
10 </head>
11
12 <body>
13 <div id="root">
14 <table>
15 <tbody>
16 <tr is="row"></tr>
17 <tr is="row"></tr>
18 <tr is="row"></tr>
19 </tbody>
20 </table>
21 </div>
22 <script>
23 // 子组件
24 Vue.component("row", {
25 // 子组件定义数据data的方法必须是一个函数返回,不能像根对象一样
26 data: function () {
27 return {
28 content: 'this is a row'
29 }
30 },
31 template: '<tr><td>{{content}}</td></tr>'
32 })
33
34 var vm = new Vue({
35 el: "#root",
36 })
37 </script>
38 </body>
39
40 </html>

Vue中的 ref 引用的内容
<!DOCTYPE html>
<html lang="en">
<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>ref</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- 在vue当中,可以通过ref获取dom节点 -->
<div ref='hello' @click="handleClick">hello world</div>
</div>
<script>
var vm = new Vue({
el: "#root",
methods: {
handleClick: function () {
// 获取dom中的内容
// this.$refs.hello 获取ref=hello的dom节点
alert(this.$refs.hello.innerHTML)
}
}
})
</script>
</body>
</html>

vue实现计数器功能
<!DOCTYPE html>
<html lang="en">
<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="./vue.js"></script>
</head>
<body>
<div id="root">
<counter ref='one' @change="handleChange"></counter>
<counter ref='two' @change="handleChange"></counter>
<!-- 求和 -->
<div>{{total}}</div>
</div>
<script>
// 子组件
Vue.component('counter', {
template: '<div @click="handleClick">{{number}}</div>',
data: function () {
return {
number: 0
}
},
methods: {
handleClick: function () {
this.number++
// 向外发送change事件
this.$emit('change')
}
}
})
var vm = new Vue({
el: "#root",
data: {
total: 0
},
methods: {
handleChange: function () {
this.total = this.$refs.one.number + this.$refs.two.number
// console.log(this.$refs.one.number)
// console.log(this.$refs.two.number)
}
}
})
</script>
</body>
</html>

父子组件间传值
父组件向子组件传递数据
- 父组件通过属性的形式向子组件传递数据。
- 父组件可以随意的向子组件传递参数。
- 但是子组件绝对不能去修改父组件传进来的参数(单向数据流)。
1 <!DOCTYPE html>
2 <html lang="en">
3
4 <head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>父子间组件传值</title>
9 <script src="./vue.js"></script>
10 </head>
11
12 <body>
13 <div id="root">
14 <!-- 父组件都是通过属性的形式向子组件传递数据 -->
15 <counter :count="1"></counter>
16 <counter :count="2"></counter>
17 </div>
18
19 <script>
20
21 // 局部组件
22 var counter = {
23 // props 表示子组件接受父组件的内容
24 props: ['count'],
25 data: function () {
26 return {
27 // 子组件自己的data number值
28 number:this.count
29 }
30 },
31 template: "<div @click='handleClick'>{{number}}</div>",
32 methods: {
33 // 点击累加方法
34 handleClick: function () {
35 // 父组件可以随意的向子组件传递参数
36 // 但是子组件绝对不能去修改父组件传进来的参数 单向数据流
37 // 因此修改自己的Number值
38 this.number++
39 },
40 }
41 }
42
43 var vm = new Vue({
44 el: "#root",
45 // 注册局部组件.
46 components: {
47 counter: counter,
48 }
49 })
50 </script>
51
52 </body>
53
54 </html>

子组件向父组件传值
<!DOCTYPE html>
<html lang="en">
<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="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- 父组件都是通过属性的形式向子组件传递数据 -->
<counter :count="3" @change="handleChange"></counter>
<counter :count="2" @change="handleChange"></counter>
<div>{{total}}</div>
</div>
<script>
// 局部组件
var counter = {
// props 表示子组件接受父组件的内容
props: ['count'],
data: function () {
return {
// 子组件自己的data number值
number:this.count
}
},
template: "<div @click='handleClick'>{{number}}</div>",
methods: {
// 点击累加方法
handleClick: function () {
// 父组件可以随意的向子组件传递参数
// 但是子组件绝对不能去修改父组件传进来的参数 单向数据流
// 因此修改自己的Number值
this.number++
// 向外触发事件,后可以跟参数
this.$emit('change',1)
},
}
}
var vm = new Vue({
el: "#root",
data:{
total:5,
},
// 注册局部组件.
components: {
counter: counter,
},
methods:{
handleChange:function(step){
// step = 1 步长为2
// 求和等于默认值+点击一下的步长
this.total += step
}
}
})
</script>
</body>
</html>

组件参数校验与非props特性
组件参数校验
组件参数校验是指:父组件向子组件传递参数的时候,子组件有权向父组件提出参数的形式和要求,并检验父组件传进的参数是否合乎要求。
<!DOCTYPE html>
<html lang="en">
<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>组件参数校验与非props特性</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- <child :content="123"></child> -->
<child content="123"></child>
</div>
<script>
Vue.component('child',{
props:{
// content:String, // 子组件接收到的content数据,必须是一个字符串类型
// content:[Number,String] // 子组件接收到的content数据,要么是字符串,要么是数字
content:{ // 接收content
type:String, //类型type必须是string
// required:true, // 表示content必需传
// default:'default value', // 如果没有传进来,默认显示这个
validator:function(value){ // 校验器校验传入的内容长度必须大于5
return (value.length>5)
},
}
},
template:'<div>{{content}}</div>',
})
var vm = new Vue({
el:"#root",
})
</script>
</body>
</html>

非 Props 特性
Props 特性是指:当你的父组件使用子组件的时候通过属性向子组件传值的时候,恰好子组件里面声明了对父组件传递过来的属性的接收。

非Props 特性是指:父组件向子组件传递了一个属性,但是子组件并没有props接收的内容,也就是说,子组件并没有声明要接受父组件传递进来的属性。

非Props 特性特点一:如果子组件没人接收父组件传进的属性,则子组件不能使用父组件传进的值。
<!DOCTYPE html>
<html lang="en">
<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>非 Props 特性</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- <child :content="123"></child> -->
<child content="hell"></child>
</div>
<script>
Vue.component('child', {
// props: {
// content: { // 接收content
// type: String, //类型type必须是string
// }
// },
// content 找不到,就会报错
template: '<div>{{content}}</div>',
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>

非Props 特性特点二:DOM中会保留父组件传递给子组件的属性标识
<!DOCTYPE html>
<html lang="en">
<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>非 Props 特性</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- <child :content="123"></child> -->
<child content="hell"></child>
</div>
<script>
Vue.component('child', {
// props: {
// content: { // 接收content
// type: String, //类型type必须是string
// }
// },
template: '<div>hello</div>',
// content 找不到,就会报错
// template: '<div>{{content}}</div>',
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>

给组件绑定原生事件
很简单,在绑定事件的click后面加一个修饰符就行。
修饰符为 .native
<!DOCTYPE html>
<html lang="en">
<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="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- 原生点击事件 -->
<child @click.native="handleClick"></child>
</div>
<script>
Vue.component('child',{
template:'<div @click="handleChildClick">Child</div>',
})
var vm = new Vue({
el:"#root",
methods:{
handleClick:function(){
alert('click')
}
}
})
</script>
</body>
</html>

非父子组件间的传值
情景分析
我们可以把一个网页拆分成很多个部分,每个部分就是我们代码中是我一个组件,如下面的一张图:

如果 1 2 层需要进行传值,则为父子组件之间的传值,通信方式在之前的内容讲到过。

如果 1 3 层进行传值,则为非父子组件间的传值,应该怎么办呢?

第一中方式:和父子组件间传值一样,一层一层的传递,第一层传给第二层,第二层在传给第三层,反之亦然。但是这种传值方式显然不方便太繁琐。
加入 3 3 层进行的非父子组件传值,又会是怎样的处理方法呢?

这种情况显然更加不适合层层传值,即第三层传给第二层,第二层传给第一层,第一层传给第二层,第二层传给第三层,累死了!代码变得非常的复杂。
非父子组件传值解决方法
第一种方法,我们可以使用 VUE 官方提供的一个数据层的框架,名字叫做 VUEX 来解决,但是使用有难度。
第二种方法,使用 发布订阅模式 来解决非父子组件的传值问题,在vue中叫做 总线机制 。
使用总线机制解决非父子组件传值问题
<!DOCTYPE html>
<html lang="en">
<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>非父子组件间的传值(Bus|总线|发布订阅模式|观察者模式)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child content="Jayvee"></child>
<child content="Wong"></child>
</div>
<script>
Vue.prototype.bus = new Vue()
Vue.component('child',{
data:function(){
return{
selfContent:this.content
}
},
template:'<div @click="handleClick">{{selfContent}}</div>',
props:{
content:String,
},
methods:{
handleClick:function(){
this.bus.$emit('change',this.selfContent)
}
},
mounted:function(){
var this_ = this
this.bus.$on('change',function(msg){
this_.selfContent = msg
})
}
})
var vm =new Vue({
el:"#root",
})
</script>
</body>
</html>

VUE 中的插槽 - slot
父组件通过传值的方式向子组件添加标签
<!DOCTYPE html>
<html lang="en">
<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>vue中的插槽(slot)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child content="<p>wjw</p>"></child>
</div>
<script>
Vue.component('child',{
props:['content'],
template:'<div><p>hello</p><div v-html="this.content"></div></div>'
})
var vm = new Vue({
el:"#root",
})
</script>
</body>
</html>

使用插槽
<!DOCTYPE html>
<html lang="en">
<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>vue中的插槽(slot)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child>
<p>wjw</p>
</child>
</div>
<script>
Vue.component('child',{
template:'<div><p>hello</p><slot>默认内容</slot></div>'
})
var vm = new Vue({
el:"#root",
})
</script>
</body>
</html>

传入header和footer
<!DOCTYPE html>
<html lang="en">
<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>vue中的插槽(slot)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<body-content>
<div slot='header' class="header">header</div>
<div slot='footer' class="footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{
template:`<div>
<slot name='header'></slot>
<div class="content">content</div>
<slot name='footer'></slot>
</div>`
})
var vm = new Vue({
el:"#root",
})
</script>
</body>
</html>

Vue中的作用域插槽
<!DOCTYPE html>
<html lang="en">
<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>vue中的作用域插槽(slot)</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child>
<template slot-scope="props">
<li>{{props.item}} -- hello</li>
</template>
</child>
</div>
<script>
Vue.component('child', {
data: function () {
return {
list: [1, 2, 3, 4]
}
},
template: `<div>
<ul>
<slot v-for="item of list" :item=item></slot>
</ul>
</div>`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>

Vue的动态组件与 v-once 指令
点击按钮实现两个组件显隐切换
<!DOCTYPE html>
<html lang="en">
<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>VUE的动态组件与v-once指令</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one',{
template:"<div>child-one</div>"
})
Vue.component('child-two',{
template:"<div>child-two</div>"
})
var vm = new Vue({
el:'#root',
data:{
type:'child-one'
},
methods:{
handleBtnClick:function(){
this.type = this.type === 'child-one'?'child-two':'child-one'
},
}
})
</script>
</body>
</html>

动态组件
<!DOCTYPE html>
<html lang="en">
<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>VUE的动态组件与v-once指令</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- component是vue自带的,表示动态组件 -->
<component :is="type"></component>
<!-- <child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two> -->
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one',{
template:"<div>child-one</div>"
})
Vue.component('child-two',{
template:"<div>child-two</div>"
})
var vm = new Vue({
el:'#root',
data:{
type:'child-one'
},
methods:{
handleBtnClick:function(){
this.type = this.type === 'child-one'?'child-two':'child-one'
},
}
})
</script>
</body>
</html>

V-once 节约性能,提高静态文件的展示效率
<!DOCTYPE html>
<html lang="en">
<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>VUE的动态组件与v-once指令</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<!-- component是vue自带的,表示动态组件 -->
<!-- <component :is="type"></component> -->
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one',{
template:"<div v-once>child-one</div>"
})
Vue.component('child-two',{
template:"<div v-once>child-two</div>"
})
var vm = new Vue({
el:'#root',
data:{
type:'child-one'
},
methods:{
handleBtnClick:function(){
this.type = this.type === 'child-one'?'child-two':'child-one'
},
}
})
</script>
</body>
</html>

来源:https://www.cnblogs.com/wjw1014/p/10271242.html