3 - Vue组件系统

概要

vue.js既然是框架,那就不能只是简单的完成数据模板引擎的任务,它还提供了页面布局的功能。本文详细介绍使用vue.js进行页面布局的强大工具,vue.js组件系统。

组件的分类

每一个新功能的诞生,都是为了解决特定问题的。vue中的组件分为两种,全局组件和局部组件。

通常一个应用会以一棵嵌套的组件树的形式来组织, 你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

全局组件

全局组件是一个可复用的vue实例,但是不管用不用都会创建,会占用内存,而且是不会被垃圾回收的,所以不是很好。

通过Vue.component()创建一个全局组件之后,我们可以在一个通过 new Vue 创建的 Vue 根实例【挂载的标签中,比如<div id="app"> ....  </div>】,把这个组件作为自定义元素来使用。

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="app">
    <!--第二步,使用-->
    <global_component></global_component>
  </div>
  <script>
    // 第一步,注册
    Vue.component("global_component", {
      template: `
        <div>
            <h2>Hello Vue</h2>
        </div>
      `
    });

    new Vue({
      el: "#app",
    });
  </script>
</body>
</html>
复制代码

组件中template中的元素要通过div包含一层,必须外面包含一层!!!

组件中template中的元素要通过div包含一层,必须外面包含一层!!!

组件中template中的元素要通过div包含一层,必须外面包含一层!!!

组件中template中的元素要通过div包含一层,必须外面包含一层!!!

组件中template中的元素要通过div包含一层,必须外面包含一层!!!

let App = {

  里面再写子组件

}

然后下面再调用App

这个是编程规范!!!

局部组件

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

全局组件始终是存在的,除非程序结束,如果组件越来越大,那么程序所占用的空间和消耗的性能就会更大。

所以我们需要局部组件。不用的时候,被垃圾回收。

局部组件的原理是:通过components 把js对象(let先定义的一个对象)注册为组件(components的属性名和对象名一样)

局部组件的两种使用方式

局部组件的第一种使用方式

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="component-demo">
    <!--第三步:在根元素当中使用它-->
    <!--第一种使用方式,会把当前div渲染进DOM-->
    <my-header></my-header>
  </div>
  <script>
    // 第一步:定义一个局部组件,其实就是一个变量,它是一个object类型
    // 备注:属性与全局组件是一样的
    let Header = {
      template: `
        <button @click="count++">{{ count }}</button>
      `,
      data() {
        return {
          count: 0
        }
      }
    };

    new Vue({
      el: "#component-demo",
      // 第二步,需要在根实例当中使用它
      components: {
        'my-header': Header
      }
    });
  </script>
</body>
</html>
复制代码

局部组件的第二种使用方式

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="component-demo">
  </div>
  <script>
    // 第一步:定义一个局部组件,其实就是一个变量,它是一个object类型
    // 备注:属性与全局组件是一样的
    let Header = {
      template: `
        <button @click="count++">{{ count }}</button>
      `,
      data() {
        return {
          count: 0
        }
      }
    };

    new Vue({
      el: "#component-demo",
      // 第三步 在Vue实例中使用它
    // 第二种使用方式 不会将div渲染进DOM,以template为根元素
template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用它 components: { 'my-header': Header } }); </script> </body> </html>
复制代码

对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象(let 对象)。

组件的选项

全局组件和局部组件都不可以有#el

因为组件是可复用的 Vue 实例,所以它们与 new Vue()相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像el这样选项只有根实例才特有。

如下示例,组件增加了data选项

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="app">
    <!--第二步,使用-->
    <global_component></global_component>
  </div>
  <script>
    // 第一步,注册
    Vue.component("global_component", {
      data: function () {
        return {
          count: 0
        }
      },
      template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
    });

    new Vue({
      el: "#app",
    });
    
  </script>
</body>
</html>
复制代码

组件的复用

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="app">
    <!--第二步,使用-->
    <global_component></global_component>
    <global_component></global_component>
    <global_component></global_component>
  </div>
  <script>
    // 第一步,注册
    Vue.component("global_component", {
      data: function () {
        return {
          count: 0
        }
      },
      template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
    });

    new Vue({
      el: "#app",
    });
    
  </script>
</body>
</html>
复制代码

注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。

data必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝, 也可以写成如下形式

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
</head>
<body>
  <div id="app">
    <!--第二步,使用-->
    <global_component></global_component>
    <global_component></global_component>
    <global_component></global_component>
  </div>
  <script>
    // 第一步,注册
    Vue.component("global_component", {
      data(){
        return {
          count: 0
        }
      },
      template: `<button v-on:click="count++">You clicked me {{ count }} times.</button>`
    });

    new Vue({
      el: "#app",
    });

  </script>
</body>
</html>
复制代码

子组件

在局部组件中使用子组件,示例如下

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
  <style>
    body {
      margin: 0;
    }
    .box {
      width: 100%;
      height: 50px;
      background-color: #2aabd2;
    }

  </style>
</head>
<body>
  <div id="component-demo">
  </div>
  <script>
   // 第一步:定义局部组件和子组件,并在局部组件中使用子组件 // 定义一个局部组件,其实就是一个变量,它是一个object类型 // 属性与全局组件是一样的 let Fcontent = { template: ` <div> <span>这是头条</span> </div> ` }; let Header = { template: ` <div v-bind:class='{box: isBox}'> <button @click="count++">{{ count }}</button> <first-content></first-content> // 在局部组件中使用子组件的元素 </div> `, data() { return { count: 0, isBox: true } }, components: { 'first-content': Fcontent // 在局部组件中注册子组件 } }; new Vue({ el: "#component-demo", // 第二种使用方式,不会将div渲染进DOM,以template为根元素 template: `<my-header></my-header>`, // 第二步,需要在根实例当中使用局部组件 components: { 'my-header': Header } }); </script> </body> </html>
复制代码

组件之间同学

父子组件通信

1.定义子组件和父组件,在父组件中通过components注册子组件获得自定义元素

2.在父组件的template中使用子组件对应的自定义元素,同时绑定一个自定义属性,传入要传入的参数

3.子组件中通过props定义一个数组来接受父组件传过来的自定义属性,并在自己的template中渲染

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
  <style>
    body {
      margin: 0;
    }
    .box {
      width: 100%;
      height: 50px;
      background-color: #2aabd2;
    }

  </style>
</head>
<body>
  <div id="component-demo">
  </div>
  <script>
    // 定义一个局部组件,其实就是一个变量,它是一个object类型
    // 属性与全局组件是一样的

    let Fcontent = {
      template: `
        <div>
          <span>这是头条</span>
          {{ fdata }}
        </div>
      `,
      props: ['fdata']
    };

    let Header = {
      template: `
        <div v-bind:class='{box: isBox}'>
          <button @click="count++">{{ count }}</button>
          <first-content :fdata="fathData"></first-content>
        </div>
      `,
      data() {
        return {
          count: 0,
          isBox: true,
          fathData: "我是你爸爸~~~"
        }
      },
      components: {
        'first-content': Fcontent
      }
    };

    new Vue({
      el: "#component-demo",
      // 第二种使用方式,不会将div渲染进DOM,以template为根元素
      template: `<my-header></my-header>`,
      // 第二步,需要在根实例当中使用它
      components: {
        'my-header': Header
      }
    });
  </script>
</body>
</html>
复制代码

子父组件通信

1.定义子组件和父组件,在父组件中通过components注册子组件获得自定义元素

2.在父组件的template中使用子组件对应的自定义元素,同时mount的时候监听一个自定义事件,没有子组件触发则永远不会执行,后面加一个回调函数,进行各种运算

3.子组件  绑定一个click事件,这个事件对应一个函数,函数内通过 this.$emit(父组件监听的事件),value)发送事件给父组件(在父组件上触发一个事件)

备注:$emit是一个内建的方法

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
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../statics/vue.min.js"></script>
  <style>
    body {
      margin: 0;
    }
    .box {
      width: 100%;
      height: 50px;
      background-color: #2aabd2;
    }
 
  </style>
</head>
<body>
  <div id="component-demo">
  </div>
  <script>
    // 定义一个局部组件,其实就是一个变量,它是一个object类型
    // 属性与全局组件是一样的
 
    let Fcontent = {
      template: `
        <div>
          <button v-on:click="myClick">放大父组件字体</button>
        </div>
      `,
      methods: {
        myClick: function () {
          console.log(this);
          this.$emit('change-font', 0.1);
          console.log(this);
        }
      }
    };
 
    let Header = {
      /*
      template: `
        <div v-bind:class='{box: isBox}'>
          <first-content v-on:change-font="changeFont"></first-content>
          <span :style="{ fontSize: postFontSize + 'em' }">Hello Vue</span>
        </div>
      `,
      */
      template: `
        <div v-bind:class='{box: isBox}'>
          <first-content v-on:change-font="postFontSize += $event"></first-content>
          <span :style="{ fontSize: postFontSize + 'em' }">Hello Vue</span>
        </div>
      `,
      data() {
        return {
          count: 0,
          isBox: true,
          fathData: "我是你爸爸~~~",
          postFontSize: 1
        }
      },
      components: {
        'first-content': Fcontent
      },
      methods: {
        //changeFont: function (value) {
        //  this.postFontSize += value;
        //}
      }
    };
 
    const VM = new Vue({
      el: "#component-demo",
      // 第二种使用方式,不会将div渲染进DOM,以template为根元素
      template: `<my-header></my-header>`,
      // 第二步,需要在根实例当中使用它
      components: {
        'my-header': Header
      }
    });
  </script>
</body>
</html>  

课堂例子:

 

非关系组件之间通信

一个监听

一个emit触发

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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../statics/vue.min.js"></script>
</head>
<body>
    <div id="app">
            <com-main></com-main>
    </div>
 
    <script>
 
        let pizza = new Vue(); //1.创建一个中央事件总线
 
        let Alex = {
            template: `
                <div>
                    <button @click="alexClick">点击向沛齐道歉</button>
                </div>
            `,
            methods: {
                alexClick: function () {
 
                    pizza.$emit("alex_apo", "原谅我吧,请你大保健~~~");  //2.通过中央事件总线的emit发送一个别人的监听事件和要发送的内容
                }
            },
        };
 
        let peiQi = {
            template: `
                <div v-show="isShow">原谅你了~~~</div>
            `,
            mounted () {
                pizza.$on("alex_apo", function (alexsay) {  //3.通过中央事件总线的$.on监听一个事件(等待别人发送过来触发),同时跟一个回调函数,来处理发过来的内容
                    if ( alexsay ) {
                        console.log("原谅你了~~~");
                    }
                });
            },
            data () {
                return {
                    isShow: false
                };
            }
        };
 
        let App = {     // 蓝色字体为组件编程的编程习惯或者说是编程规范
            template: `
                <div id="app">
                    <alex></alex>
                    <peiqi></peiqi>
                </div>
            `,
            components: {
                'alex': Alex,
                'peiqi': peiQi
            }
        };
 
        new Vue({
            el: "#app",
            template: '<app></app>',
            components: {
                'app': App,
            },
        })
 
    </script>
 
</body>
</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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../statics/vue.js"></script>
</head>
<body>
    <div id="mixin-demo">
        <my-alex></my-alex>
        <p></p>
        <my-peiqi></my-peiqi>
    </div>
 
    <script>
        let mixs = {
            methods: {
                show: function (name) {
                    console.log(`${name} is here!`);
                },
                hide: function (name) {
                    console.log(`${name} is gone!`);
                },
            }
        };
 
        // Vue.component("my-comp", {
        //     template: `
        //               <div><button v-on:click="showAlex">点我显示alex</button></div>
        //     `,
        //     methods: {
        //         showAlex: function () {
        //             alert("alex is here!");
        //         },
        //     }
        // });
        //
        // new Vue({
        //     el: "#mixin-demo",
        // });
 
        let myAlex = {
            template: `
                    <div>
                        <button @click="show('alex')">点我显示alex</button>
                        <button @click="hide('alex')">点我隐藏alex</button>
                    </div>
 
            `,
            // methods: {
            //     show: function (name) {
            //         console.log(`${name} is here!`);
            //     },
            //     hide: function (name) {
            //         console.log(`${name} is gone!`);
            //     }
            // }
            mixins: [mixs],
        };
 
        let myPeiqi = {
            template: `
                    <div>
                        <button @mouseenter="show('peiqi')">鼠标移入显示沛齐</button>
                        <button @mouseleave="hide('peiqi')">鼠标离开隐藏沛齐</button>
                    </div>
            `,
            // methods: {
            //     show: function (name) {
            //         console.log(`${name} is here!`);
            //     },
            //     hide: function (name) {
            //         console.log(`${name} is gone!`);
            //     }
            // },
            mixins: [mixs],
        };
 
        new Vue({
            el: "#mixin-demo",
            components: {
                "my-alex": myAlex,
                "my-peiqi": myPeiqi,
            }
        })
    </script>
 
</body>
</html> 

插槽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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .nav-link {
            width: 100px;
            height: 100px;
            background-color: #2aabd2;
            float: left;
            margin-left: 5px;
            text-align: center;
            line-height: 100px;
        }
 
    </style>
    <script src="../statics/vue.js"></script>
</head>
<body>
    <div id="app01">
        <com-content>登录</com-content>
        <com-content>注册</com-content>
        <com-content>最热</com-content>
        <com-content>段子</com-content>
        <com-content>42区</com-content>
        <com-content>图片</com-content>
    </div>
 
    <script>
        Vue.component('com-content', {
          template: `
            <div class="nav-link">
              <slot></slot>
            </div>
          `
        });
 
        new Vue({
            el: "#app01",
        })
    </script>
 
</body>
</html>  

具名插槽

有些时候我们需要多个插槽,可以把内容和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
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .nav-link {
            width: 100px;
            height: 100px;
            background-color: #2aabd2;
            float: left;
            margin-left: 5px;
            text-align: center;
            line-height: 100px;
        }
    </style>
    <script src="../statics/vue.js"></script>
</head>
<body>
    <div id="app01">
        <base-layout>
            <template slot="header">
                <h1>这是标题栏</h1>
            </template>
            <template>
                <h2>这是内容</h2>
            </template>
            <template slot="footer">
                <h3>这是页脚</h3>
            </template>
        </base-layout>
    </div>
 
    <script>
        let baseLayout = {
            template: `
                <div class="container">
                  <header>
                      <slot name="header"></slot>
                  </header>
                  <main><slot></slot></main>
                  <footer>
                      <slot name="footer"></slot>
                  </footer>
                </div>
            `
        };
 
        new Vue({
            el: "#app01",
            components: {
                "base-layout": baseLayout
            }
 
        })
    </script>
 
</body>
</html>

我们还是可以保留一个未命名插槽,这个插槽是默认插槽,也就是说它会作为所有未匹配到插槽的内容的统一出口

使用组件实现路飞导航栏

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../statics/vue.min.js"></script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        .header {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
        }
        .el-menu {
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .footer {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
        }
        .header img {
            position: absolute;
            left: 80px;
            top: -4px;
            width: 118px;
            height: 70px;
            z-index: 999;
        }

    </style>
</head>
<body>

    <div id="app">

    </div>

    <template id="header">
        <div class="header">
            <img src="https://www.luffycity.com/static/img/head-logo.a7cedf3.svg"/>
            <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal">
                <el-menu-item index="1">首页</el-menu-item>
                <el-menu-item index="2">免费课程</el-menu-item>
                <el-menu-item index="3">轻课</el-menu-item>
                <el-menu-item index="4">学位课程</el-menu-item>
                <el-menu-item index="5">智能题库</el-menu-item>
                <el-menu-item index="6">公开课</el-menu-item>
                <el-menu-item index="7">内部教材</el-menu-item>
                <el-menu-item index="8">老男孩教育</el-menu-item>
            </el-menu>
        </div>

    </template>



    <template id="footer">
        <div class="footer">

            <el-menu class="el-menu-demo" mode="horizontal" background-color="black">
                <el-menu-item index="1">关于我们</el-menu-item>
                <el-menu-item index="2">联系我们</el-menu-item>
                <el-menu-item index="3">商业合作</el-menu-item>
                <el-menu-item index="4">帮助中心</el-menu-item>
                <el-menu-item index="5">意见反馈</el-menu-item>
                <el-menu-item index="6">新手指南</el-menu-item>
            </el-menu>

        </div>

    </template>



    <script>

        let pageHeader = {
            template: "#header",
            data() {
                return {
                    activeIndex: "1",
                }
            }
        };

        let pageFooter = {
            template: "#footer",
        };

        let App = {
            template: `
                <div>
                    <div>
                        <page-header></page-header>

                    </div>
                    <div>
                        <page-footer></page-footer>
                    </div>
                </div>
            `,
            components: {
                'page-header': pageHeader,
                'page-footer': pageFooter,
            }
        };

        new Vue({
            el: "#app",
            template: `<app></app>`,
            components: {
                'app': App,
            }
        })

    </script>
</body>
</html>
复制代码

参考

https://pizzali.github.io/2018/10/27/Vue-js%E7%BB%84%E4%BB%B6%E7%B3%BB%E7%BB%9F/#more

posted @   番茄土豆西红柿  阅读(215)  评论(0)    收藏  举报
编辑推荐:
· 如何通过向量化技术比较两段文本是否相似?
· 35+程序员的转型之路:经济寒冬中的希望与策略
· JavaScript中如何遍历对象?
· 领域模型应用
· 记一次 ADL 导致的 C++ 代码编译错误
阅读排行:
· 独立项目运营一周年经验分享
· 神解释:为什么程序员怕改需求?
· 一款开源免费、通用的 WPF 主题控件包
· 独立开发,这条路可行吗?
· 【定时任务核心】究竟是谁在负责盯着时间,并在恰当时机触发任务?
TOP
点击右上角即可分享
微信分享提示