Vue组件化

组件化的诞生

由于之前的传统页面编写方式存在的问题:

  • 依赖关系混乱不好维护
  • 代码复用性低

所以Vue提供了组件化的方式: 每个组件有自己相应的css、html、js等代码,体现了组件的封装,解决了传统方式存在的问题

组件化的基本使用

具体步骤:

  1. 使用组件构造器创建组件
  2. 注册组件
  3. 使用组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<div id="app">
<!-- 使用组件 -->
<mycpn></mycpn>
</div>
<div id="app2">
<!-- 使用组件 -->
<cpn></cpn>
</div>
<script src="../js/vue.js"></script>
<script>
//1.使用组件构造器创建组件
const cpnc = Vue.extend({
//自定义的模板
template:`
<div>
<h2>我是标题</h2>
<p>我是内容</p>
</div>`
})
//2.注册组件,并起标签名(该组件为全局组件可以在多个vue实例中使用)
Vue.component('mycpn',cpnc)
const app = new Vue({
el:'#app',
data:{
message:'你好啊'
}
})

const app2 = new Vue({
el:'#app2',
data:{
message:'你好啊'
},
components:{
//cpn使用组件时的标签名
//这种为局部组件
cpn:cpnc
}
})
</script>

语法糖写法:Vue.component('mycpn',{template:

我是标题

})

组件的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<body>
<div id="root">
<school></school>
<hr>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const student = Vue.extend({
template:`
<div>
<h2>学生姓名:{{stuName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>`,
data(){
return{
stuName:'小王',
age:18
}
},
})

const school = Vue.extend({
template:`
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>`,
data(){
return{
schoolName:'家里蹲',
address:'屋头'
}
},
//组件的嵌套
components:{
student
}
})

new Vue({
el:'#root',
//局部注册
components:{
school
}
})
</script>
</body>

模板的抽离

1
2
3
4
5
6
7
8
<!-- 抽离模板 -->
<template id="cpn">
<div>
<h2>我是标题</h2>
</div>
</template>
<!-- 使用模板 -->
template:'#cpn'

父子组件的通信

父组件向子组件通信

通过props向子组件传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//子组件利用props属性接收
//方式1以数组的方式接收
props:['name','sex','age'] 简单接收
//方式2对象得复杂写法,可以进行类型的限制
props:{
name:String,
age:Number,
sex:String
}
//方式3最复杂写法
props:{
name:{
type:String, //类型
required:true //是否必要的
},
age:{
type:Number,
default:99 //默认值
},
sex:{
type:String,
required:true
}
}
//父组件以标签属性传值,注意不使用v-bind会直接以字符串形式传递
<Student :name="李四" :sex="女" :age="18"/>

子组件向父组件通信

通过自定义事件向父组件发送数据

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div class="stu">
<h2>学生名:{{name}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sendName">把学生名传给app</button>
<button @click="unbind">解绑事件</button>
</div>
</template>

<script>
export default {
name:'Student',
data(){
return{
name:'张三',
sex:'男'
}
},
methods:{
sendName(){
//触发绑定的getName事件,并传参
this.$emit('getName',this.name);
},
unbind(){
//解绑事件(只适用单个事件,多个参数传[]数据,不传参数就等于解绑所有自定义事件)
this.$off('getName');
}
}
}
</script>

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div class="app">
<h1>{{msg}}</h1>
<!-- 给子组件绑定一个自定义事件:实现子给父传数据 -->
<Student v-on:getName="getStudentName"/>
<!--@click.native表明该事件为原生事件-->
<student ref="student" @click.native="show"/>
</div>

</template>

<script>
import Student from './components/Student.vue'
export default {
name:'App',
components: { Student },
data() {
return {
msg:'你好啊!!!'
}
},
methods:{
getStudentName(name){
console.log("学生名:"+name);
}
},
mounted() {
//同样可以绑定事件
this.$refs.student.$on('getName',this.getStudentName)
},
}
</script>

父子组件之间的访问

父组件访问子组件

使用$children或$refs

  1. this.$children获得子组件数组
  2. this.refs获取标有ref属性的子组件数组,若是原生DOM则获取的是原生DOM元素

子组件访问父组件

使用$parent

  1. this.$parent获得父组件(不常用)

访问根组件

使用$root

  1. this.$root获得根组件

组件之间的访问

全局事件总线

可以任意组件间进行通信

总线特点:

 1. 对于所有组件都可见
 2. 可以调用$on、$off、$emit

Vue的原型对象刚好符合(最好放在这里)

  • 在main.js中布局全局事件总线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false

    // const demo = Vue.extend({})
    // Vue.prototype.x = new demo()

    //创建vm
    const vm = new Vue({
    el:'#app',
    render: h => h(App),
    beforeCreate(){
    Vue.prototype.$bus = this //布局全局事件总线
    }
    })
  • 进行组件间通信

    组件1(接收方,给总线绑定事件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <template>
    <div class="stu">
    <h2>学生名:{{name}}</h2>
    <h2>性别:{{sex}}</h2>
    </div>
    </template>

    <script>
    export default {
    name:'Student',
    data(){
    return{
    name:'张三',
    sex:'男'
    }
    },
    mounted(){
    this.$bus.$on('hello',((data)=>{
    console.log('我是Student组件,收到了数据',data)
    }))
    },
    beforeDestroy(){
    this.$bus.$off('hello')
    }
    }
    </script>

    <style>
    .stu{
    background-color: aquamarine;
    }
    </style>

    组件2发送数据方(调用接收方给总线绑定的事件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <template>
    <div class="school">
    <h2>学校名称:{{schoolName}}</h2>
    <h2>学校地址:{{address}}</h2>
    <button @click="sendSchoolName">把学校名给student组件</button>
    </div>
    </template>

    <script>
    export default {
    name:'School',
    data(){
    return{
    schoolName:'家里蹲',
    address:'屋头'
    }
    },
    methods:{
    sendSchoolName(){
    this.$bus.$emit('hello', this.schoolName)
    }
    }

    }
    </script>

    <style>
    .school{
    background-color: aquamarine;
    }
    </style>

    消息订阅与发布

同样用于任意组件间通信

基于pubsub-js

  1. 安装pubsub-js : npm i pubsub-js

  2. 引入js :import pubsub from ‘pubsub-js’

  3. 接收数据方需要订阅消息:

    1
    2
    3
    4
    5
    6
    7
    8
    mounted(){
    this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
    console.log('有人发布了'+msgName+'消息',+data)
    })
    },
    beforeCreate(){
    pubsub.unsubscribe(this.pubId)
    }
  4. 发送数据方需要发布消息:

    1
    2
    3
    4
    5
    methods:{
    sendSchoolName(){
    pubsub.publish('hello',666)
    }
    }

    nextTick

  5. 语法:this.$nextTick('xxx',数据)

  6. 作用:在下一次DOM更新结束后执行其指定的回调

slot插槽

插槽的作用:

  1. 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  2. 让使用者可以决定组件内部的一些内容到底展示什么。

1.默认插槽

子组件:

1
2
3
4
5
6
7
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="container">
<Category title="美食" >
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>

<Category title="游戏" >
<ul>
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
</Category>

<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>

2.具名插槽

子组件中的插槽加个name,以便引用

1
2
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>

父组件中的组件用slot属性指明插入哪个插槽(同样可以用v-slot:center)

1
2
3
4
<Category title="美食" >
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
<a slot="footer" href="http://www.baidu.com">更多美食</a>
</Category>

3.作用域插槽

子组件可以通过slot向插槽使用者传输数据

1
2
3
4
5
6
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>
</div>
</template>

父组件 必须用template标签包裹,不然接收不到数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="container">

<Category title="游戏">
<template scope="wht">
<ul>
<li v-for="(g,index) in wht.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>

<Category title="游戏">
<template scope="{games}">
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
</template>
</Category>

</div>
</template>

mixin混入

混合js文件

1
2
3
4
5
6
7
export const mixin = {
methods:{
showName(){
alert(this.name)
}
}
}
  • 局部混合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //引入混合(局部混合)
    import {mixin} from '../mixin'
    export default {
    name:'Student',
    data(){
    return{
    name:'张三',
    sex:'男'
    }
    },
    mixins:[mixin]
    }
  • 全局混入

    1
    2
    3
    //全局混合
    import {mixin} from './mixin'
    Vue.mixin(mixin)