1. 组件化开发

1.1. 认识组件化

1.1.1. 什么是组件化

组件化是讲一个页面进行拆分为一个一个小的功能块,每个功能块完成对应的功能,如下图。

../_images/模块化.png

1.1.2. 组件化的思想

  • 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。

  • 任何的应用都会被抽象成一颗组件树。

1.2. 注册组件

1.2.1. 基本步骤

组件的注册分如下3个阶段

  1. 创建组件构造器

  2. 注册组件

  3. 使用组件

1.2.2. 注册代码图

../_images/组件步骤.png
  1. 其中调用Vue.extend()创建的是一个组件构造器。

  2. 调用Vue.component()是将刚才的组件构造器注册为一个组件。

  3. 组件必须挂载在某个Vue实例下,否则它不会生效。

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--   使用-->
    <demo-cpt></demo-cpt>
    <demo-cpt></demo-cpt>
    <demo-cpt></demo-cpt>

</div>
<script src="../js/vue.js"></script>
<script>
    // 创建
    const demoCpt = Vue.extend(
        {
            template: `
            <div>
                <h2> 组件标题</h2>
                <p> 组件内容</p>
            </div>
            `
        }
    )
    // 注册
    Vue.component("demo-cpt", demoCpt)
    let app = new Vue({
        el: "#app",
        data: {
            cnt: 1,
        }
    })
</script>
</body>
</html>

1.3. 组件其他补充

1.3.1. 全局组件

通过Vue.component()注册的组件为全局的组件。意味这个组件是可以全局使用的。

Vue.component("demo-cpt",demoCpt)

1.3.2. 局部组件

如果在特定的vue下的components属性指定的组件为局部组件,只能在特定的父组件使用的。

let app = new Vue({
        el: "#app",
        data: {
            cnt: 1,
        },
        components:{
            "demo-cpt2": demoCpt2
        }
    })

1.3.3. 父子组件

组件和组件是有一定的层次关系的, 其中其中父子组件的是而非常重要的关系。

../_images/父子组件.png

代码如下:

 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>
    <div id="app">
        <!--    使用-->
        <demo-cpt2>
            <demo-cpt></demo-cpt>
        </demo-cpt2>


    </div>
    <script src="../js/vue.js"></script>
    <script>
        // 创建
        const demoCpt = Vue.extend({
            template: `
            <div>
                <h2> 组件标题</h2>
            </div>
            `
        })
        const demoCpt2 = Vue.extend({
            template: `
            <div>
                <h2> 组件标题2</h2>
                <p>haha</p>
                <demo-cpt></demo-cpt>
                <p>haha</p>
            </div>
            `,
            components: {
                "demo-cpt": demoCpt
            }
        })

        let app = new Vue({
            el: "#app",
            data: {
                cnt: 1,
            },
            components: {
                "demo-cpt2": demoCpt2
            }
        })
    </script>
</body>

</html>

1.3.4. 模块的分离写法

上面我们可以看到写一个组件内部还包含了html的代码,非常不方便管理和维护。可以通过如下方式进行改造。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <!--    使用-->

    <demo-cpt></demo-cpt>
    <demo-cpt2></demo-cpt2>

</div>


<script src="../js/vue.js"></script>

<script type="text/x-template" id="demoCpt">
    <div>
        <h2> 组件标题</h2>
    </div>
</script>

<template  id="demoCpt2">
    <div>
        <h2> 组件标题</h2>
    </div>
</template>


<script>

    // 创建
    Vue.component('demo-cpt',
        {
            template: '#demoCpt'
        }
    )
    Vue.component('demo-cpt2',
        {
            template: '#demoCpt2'
        }
    )
    let app = new Vue({
        el: "#app",
        data: {
            cnt: 1,
        },
    })
</script>
</body>
</html>

1.4. 组件数据存放

组件内的数据有个data属性,但是这个是一个函数的,通过这个函数返回一个dict对象即可。

详细代码如下:

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
<demo-cpt></demo-cpt>
</div>


<script src="../js/vue.js"></script>


<template  id="demoCpt">
    <div>
       <button @click="sub">-</button>
            {{count }}
        <button @click="add">+</button>
    </div>
</template>


<script>

    // 创建
    Vue.component('demo-cpt',
        {
            template: '#demoCpt',
            data(){
                // 组件是复用的,保障每组件实例对象是有独立的数据地址空间的。使用函数解决。
                return {
                    count: 1
                }

            },
            methods:{
                add(){
                    return this.count++
                },
                sub(){
                    return this.count--
                }

            }
        }
    )

    let app = new Vue({
        el: "#app",
        data: {
            cnt: 1,
        },
    })
</script>
</body>
</html>

Note

组件的data必须是一个函数的,不能是通过的字典,因为我们组件是复用的,组件复用后的数据一定是独立的。

1.5. 父子组件通信

官方文档中给出了父子组件的通信方式。

  • 通过props向子组件传递数据

  • 通过事件向父组件发送消息

../_images/父子通信.png

1.5.1. 父级向子级传递

父组件向子组件创建数据,可以通过子组件提供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
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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <label>
        <input type="text" v-model="t1"> 第1个组件的值
    </label>
    <label>
        <input type="text" v-model="t2"> 第2个组件的值
    </label>
    <!--    使用-->
    <label> 第一个组件实例</label>
    <demo-cpt :title="t1"></demo-cpt>
    <label> 第2个组件实例</label>
    <demo-cpt :title="t2"></demo-cpt>

</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
        <h2>{{title}}</h2>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
        props: {
            "title": {
                type: String,
                default: "default title"
            }
        },
        data() {

            return {
            }
        },

    }
    let app = new Vue({
        el: "#app",
        data: {
           t1: "abc",
            t2: "def",
        },
        components: {
            "demo-cpt": demoCpt
        },
        methods: {

        }

    })
</script>
</body>
</html>

1.5.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
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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">



    <!--    使用-->
    <label> 第一个组件实例</label>
    <demo-cpt @item-click="getSubInfo" ></demo-cpt>

    <label >从子组件获取的值</label>
    <h2> {{ subInfo}}</h2>

</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
       <button v-for="item in categories" @click="bntClick(item)"> {{item.name }}</button>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
        props: {
            "title": {
                type: String,
                default: "default title"
            }
        },
        data() {

            return {
                categories:[
                    {id:"123","name":"热门推荐"},
                    {id:"124","name":"手机数码"},
                    {id:"125","name":"电脑办公"},
                    {id:"126","name":"家用家电"},
                ]
            }
        },
        methods: {
            bntClick(item) {
                console.log(item)
                this.$emit("item-click",item)
            }
        }

    }
    let app = new Vue({
        el: "#app",
        data: {
            subInfo: {}
        },
        components: {
            "demo-cpt": demoCpt
        },
        methods: {
            getSubInfo(item){
                this.subInfo= item.name
                console.log(item)
            }

        },


    })
</script>
</body>
</html>

1.5.3. 父访问子组件数据

父组件访问子组件:使用$children或$refs

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="getSubInfo">点击展示子组件信息</button>
    <demo-cpt ref="aaa"></demo-cpt>

</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
       <h2>{{title}}</h2>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
        props: {
            "title": {
                type: String,
                default: "default title"
            }
        },
        methods: {
            showMessage(){
                //console.log(this.title)
                alert(this.title)
            }
        }


    }
    let app = new Vue({
        el: "#app",
        data: {

        },
        components: {
            "demo-cpt": demoCpt
        },
        methods: {
            getSubInfo(){
               // console.log(this.$children)
                this.$refs["aaa"].showMessage()
            }

        },


    })
</script>
</body>
</html>

1.5.4. 子访问父组件数据

子组件访问父组件:使用$parent

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="getSubInfo">点击展示子组件信息</button>
    <demo-cpt ref="aaa"></demo-cpt>

</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
       <h2>{{title}}</h2>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
        props: {
            "title": {
                type: String,
                default: "default title"
            }
        },
        methods: {
            showMessage(){
                //console.log(this.title)
                alert(this.title)
            }
        }


    }
    let app = new Vue({
        el: "#app",
        data: {

        },
        components: {
            "demo-cpt": demoCpt
        },
        methods: {
            getSubInfo(){
               // console.log(this.$children)
                this.$refs["aaa"].showMessage()
            }

        },


    })
</script>
</body>
</html>

1.6. 插槽slot

1.6.1. 编译作用域

父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

1.6.2. 为什么使用slot

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性。

  • 让使用者可以决定组件内部的一些内容到底展示什么。

1.6.3. slot的基本使用

在组件的模板定义中引入slot即可。

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">

    <demo-cpt>
        <button>haha</button>
    </demo-cpt>
    <demo-cpt>
        <h2>abc</h2>
        <h2>abc</h2>
        <h2>abc</h2>
    </demo-cpt>
    <demo-cpt></demo-cpt>

</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
        <h2>测试</h2>
        <slot>
            <button>默认button</button>
        </slot>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
    }
    let app = new Vue({
        el: "#app",
        data: {
            "name": "panda"
        },
        components: {
            "demo-cpt": demoCpt
        },
    })
</script>
</body>
</html>

1.6.4. 具名slot

当子组件的功能复杂时,子组件的插槽可能并非是一个,怎么方便控制用户想替换哪个slot?

这就需要给slot起个名字,让用户选择给那个slot位置进行替换。

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">

    <demo-cpt>
      <span slot="left">返回</span>
    </demo-cpt>


</div>
<script src="../js/vue.js"></script>

<template id="demoCpt">
    <div>
        <slot name="left"></slot>
        <slot name="center"></slot>
        <slot name="right"></slot>
    </div>
</template>


<script>

    const demoCpt = {
        template: "#demoCpt",
    }
    let app = new Vue({
        el: "#app",
        data: {
            "name": "panda"
        },
        components: {
            "demo-cpt": demoCpt
        },
    })
</script>
</body>
</html>