# 4. Vue 组件模块

# 什么是Vue组件和模块?

组件的出现就是为了拆分Vue实例的代码量,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。

组件化和模块化的不同:

  • 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一。
  • 组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用。

# 组件基础

# 注册全局组件

# 第一种方式

基础版

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-组件</title>
</head>

<body>
    <div id="app">
        <!-- 如果要使用组件,直接把组件的名称以HTML标签的形式,引入到页面中。 -->
        <my-com1></my-com1>
    </div>
</body>
<script>
    // 1.1 使用Vue.extend 来创建全局的Vue组件
    var com1 = Vue.extend({
        template: '<h3>这是使用 Vue.extend 创建的组件 </h3>'  // 通过 template 属性,指定了组件要展示的HTML结构。
    })

    // 1.2 使用 Vue.component('组件的名称', 创建出来的组件模板对象)
    // 注意: 组件的名称如果是驼峰,调用时需要变为以-分割。
    // 建议不使用驼峰命名组件名称。
    Vue.component('myCom1', com1)

    var vm = new Vue({
        el: '#app',
        data: {

        },
        methods: {

        }
    })
</script>

</html>
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

简写版

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-组件</title>
</head>

<body>
    <div id="app">
        <my-com></my-com>
    </div>
</body>
<script>
    Vue.component('my-com', Vue.extend({
        template: '<h3>这是使用 Vue.extend 创建的组件 </h3>'  // 通过 template 属性,指定了组件要展示的HTML结构。
    }))

    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>

</html>
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

更加简洁的方式

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-组件</title>
</head>

<body>
    <div id="app">
        <my-com></my-com>
    </div>
</body>
<script>
    Vue.component('my-com',{
        // 注意:必须有且只能有唯一的一个根元素。
        // 比如: template: '<h3>123</h3><span>456</span>' 是不允许的。
        template: '<h3>直接使用Vue.component 注册出来的组件</h3>'
    })
    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>

</html>
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

# 第二种方式(推荐)

使用绑定id的方式创建组件

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-组件</title>
</head>

<body>
    <div id="app">
        <my-com></my-com>
    </div>
    <!-- 注意:定义template组件模板标签需要在被vue控制的 #app 外面定义 -->
    <template id="my-com">
        <div>
            <h3>这是通过template标签注册的组件</h3>
            <h4>这种方式是推荐使用的,因为它可以有代码提示和代码高亮</h4>
        </div>
    </template>
</body>
<script>
    // 通过 绑定外部template标签中的id注册组件
    Vue.component('my-com', { template: '#my-com' })

    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {}
    })
</script>

</html>
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

# 注册私有组件

在Vue实例创建的时候定义components

var vm = new Vue({
    el: '#app',
    data: {}.
    methods: {},
    components: {
        // 定义内部的私有组件,key为组件名,value为对象,设置里面的template属性
        login: {
            template: '#login'
        }
    }
})
1
2
3
4
5
6
7
8
9
10
11

# 组件的data数据

使用组件可以给组件定义data数据。

语法如下:

Vue.component('my-com',{
    template: '#my-com',
    data: function (){
        return {
            msg: '这是组件中的data定义的数据!'
        }
    }
})
1
2
3
4
5
6
7
8

简写如下:

Vue.component('my-com',{
    template: '#my-com',
    data: () => ({
        msg: '这是组件中的data定义的数据!'
    })
})
1
2
3
4
5
6

# 组件的其他定义

  • 组件拥有Vue实例的全部配置。
  • 配置方法除了data有点区别外其余的基本相同。

比如下面代码所示:

  • 定义了data、methods、filters。
Vue.component('my-com',{
    template: '#my-com',
    data: () => ({
        msg: '这是组件中的data定义的数据!'
    }),
    methods: {
        add (){
            console.info('这是组件中定义的methods方法函数!')
        }
    },
    filters: {
        nameFormat: (name) => {
            return "姓名:" + name;
        }
    }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# component组件标签

Vue 提供了一个 component 的标签,其中:is属性可以用来指定要展示的组件名称。

注意:带冒号是属性绑定,那么值会当做一个表达式来解析,所以在直接指定组件名称时,应该把组件名称当做字符串做解析。

<component :is="'my-com'"></component>
1

实战情况:

需求:登录和注册切换显示

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-组件切换</title>
</head>

<body>
    <div id="app">
        <a href="" @click.prevent='comName = "login"'>用户登录</a>
        <a href="" @click.prevent='comName = "register"'>用户注册</a>
        <hr />
        <component :is="comName"></component>
    </div>
    <template id="login">
        <div>登录组件</div>
    </template>
    <template id="register">
        <div>注册组件</div>
    </template>
</body>
<script>
    Vue.component('login', {
        template: '#login',
    })
    Vue.component('register', {
        template: '#register',
    })
    var vm = new Vue({
        el: '#app',
        data: {
            comName: 'login'
        }
    })
</script>

</html>
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

# 实际开发常见组件引用方式

比如现在有一个公共组件,我们调用它

<!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">
    <script src="./lib/vue/vue.js"></script>
    <title>Vue-业务组件</title>
</head>

<body>
    <!-- 业务组件 -->
    <template id="serviceTemplate">
        <div>业务组件</div>
    </template>
    <!-- 比如上方是我们的公共组件库 -->
    <div id="app">
        <!-- 下面调用这个组件 -->
        <service-template></service-template>
    </div>
</body>
<script>
    var serviceTemplate = {
        template: '#serviceTemplate'
    }

    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        components: {
            serviceTemplate // 直接简写,把对象直接扔进去。
        }
    })
</script>

</html>
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

# 向组件传递属性

当我们需要在一些页面中向某个组件传递某个字段或属性时,可以通过内部组件绑定属性来绑定外部的属性。

下面的示例中,展示的是将appMsg和appNum传递给组件,组件显示消息。

关键属性:

props 用于装载父级属性,和data一样可定义属性。不同的是,data可读可写,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>Document</title>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <input type="button" value="传递属性给私有组件中" @click='send'>
        <br />
        <!-- 将app控制的作用域中的属性绑定到组件中,使组件内部可以动态获得外层的属性。 -->
        <my-com :msg='appMsg' :num='appNum'></my-com>
    </div>

    <template id="my-com">
        <!-- 直接输出组件中的msg。 -->
        <p>{{ msg }}</p>
    </template>

</body>
<script>

    var vm = new Vue({
        el: '#app',
        data: {
            appMsg: '',
            appNum: 0
        },
        methods: {
            send() {
                // 添加按钮触发该方法,并使其消息改变。
                this.appNum++
                this.appMsg = '这是app的消息!' + this.appNum
            }
        },
        components: {
            'my-com': {
                template: '#my-com',
                // 通过props定义组件绑定的变量。
                props: ['msg', 'num']
            }
        }
    })

</script>

</html>
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
50
51
52
53

# 组件调用父级组件方法

当需要在组件中使用父级方法时,利用事件绑定将父级方法绑定到事件中,在组件内部调用$emit触发该事件即可。

当我们需要传参时,$emit的第二个参数开始就是对应的参数列表。

<!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="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <!-- 使用事件绑定机制传递方法 -->
        <my-com @parent-click='parentClick'></my-com>
    </div>

    <template id="my-com">
        <!-- 直接输出组件中的msg。 -->
        <input type="button" value="调用父类的parentClick方法" @click='comClick'>
    </template>

</body>
<script>

    var vm = new Vue({
        el: '#app',
        methods: {
            parentClick(value) {
                console.info('这是父类的方法,被调用了,传递的参数是: --- ' + value)
            }
        },
        components: {
            'my-com': {
                template: '#my-com',
                methods: {
                    comClick() {
                        this.$emit('parent-click', '病毒')
                    }
                }
            }
        }
    })

</script>

</html>
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

# 子组件向父级组件传递数据

在上述例子中,可以将父级组件的方法传递给子组件使用,并且可以在子组件中传递参数,那么我们可以利用这一个方法,让子组件向父级组件传递数据。

<!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="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <!-- 使用事件绑定机制传递方法 -->
        <my-com @set-msg='setMsg'></my-com>
        <!-- 父级显示msg属性 -->
        <p>{{ msg }}</p>
    </div>

    <template id="my-com">
        <div>
            <input type="text" v-model='msg' />
            <input type="button" value="在子组件中设置父级组件的msg属性" @click='setMsg'>
        </div>
    </template>

</body>
<script>

    var vm = new Vue({
        el: '#app',
        data: {
            msg: ''
        },
        methods: {
            setMsg(msg) {
                this.msg = msg
            }
        },
        components: {
            'my-com': {
                template: '#my-com',
                data: () => ({ msg: '' }),
                methods: {
                    setMsg() {
                        this.$emit('set-msg', this.msg)
                    }
                }
            }
        }
    })
</script>

</html>
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
50
51
52
53
54
55

# 组件案例-评论列表

需求:

  1. 实现评论列表功能。
  2. 评论保存到localStorage中。
  3. 发表评论后列表刷新
  4. 重新进入该页面时,加载localStorage中的评论数据。
  5. 最新发表的评论要显示在第一条。
  6. 作者和评论内容不能为空。
  7. 如果当前没有评论,加载默认的列表数据。
<!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>评论列表示例Demo</title>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" />
</head>

<body>
    <div id="app">

        <comment-box @default_list='defaultList' @update_list='updateList'></comment-box>

        <ul class='list-group'>
            <li class="list-group-item" v-for='item in list' :key='item.id'>
                <span class='badge'>作者:{{ item.userName }}</span>
                {{ item.content }}
            </li>
        </ul>
    </div>

    <template id='commentBox'>
        <div>
            <div class="form-group">
                <label for="">作者:</label>
                <input type="text" class='form-control' v-model='userName' />
            </div>
            <div class="form-group">
                <label for="">评论内容:</label>
                <textarea class='form-control' v-model='content'></textarea>
            </div>
            <div class="form-group">
                <input type="button" class="btn btn-primary" value="发表评论" @click='sendComment' />
                <input type="button" class="btn btn-primary" value="清空localStorage" @click='clearLocalStorage' />
                <span style='color: red;'>{{ errorMsg }}</span>
            </div>
        </div>
    </template>
</body>
<script>

    var commentBox = {
        template: '#commentBox',
        data: () => ({
            userName: '',
            content: '',
            errorMsg: ''
        }),
        methods: {
            sendComment() {
                console.info()
                if (this.userName === undefined || this.userName === '') {
                    this.errorMsg = '作者不能为空!'
                    return
                }
                if (this.content === undefined || this.content === '') {
                    this.errorMsg = '评论内容不能为空!'
                    return
                }
                this.errorMsg = ''
                // 发表评论
                var obj = {
                    id: Date.now(),
                    userName: this.userName,
                    content: this.content
                }
                var list = JSON.parse(localStorage.getItem('cmts') || '[]')
                list.unshift(obj)
                localStorage.setItem('cmts', JSON.stringify(list))
                this.content = ''
                this.$emit('update_list')
            },
            // 清空
            clearLocalStorage() {
                localStorage.setItem('cmts', '[]')
                this.$emit('update_list')
                this.$emit('default_list')
            }
        }
    }

    var vm = new Vue({
        el: '#app',
        data: {
            list: []
        },
        created() {
            this.updateList()
            this.defaultList()
        },
        methods: {
            // 来一组默认值
            defaultList() {
                if (this.list.length == 0) {
                    let listTemp = [
                        { id: Date.now(), userName: '李白', content: '模板数据:天生我材必有用' },
                        { id: Date.now() + 1, userName: '江小白', content: '模板数据:劝君更尽一杯酒' },
                        { id: Date.now() + 2, userName: '小马', content: '模板数据:我姓马,风吹草低见牛羊的马' }
                    ]
                    this.list = listTemp
                }
            },
            // 更新列表
            updateList() {
                this.list = JSON.parse(localStorage.getItem('cmts') || '[]')
            }
        },
        components: {
            commentBox
        }
    })
</script>

</html>
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
最近更新: 2019/10/17 上午4:20:42