Twenty-five years without you

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一:组件的基本用法

组件和创建Vue实例类似,需要先注册后才能使用,Vue组件注册方式分为全局注册和局部注册,全局注册的组件在任何使用Vue的地方均可使用,局部注册的组件只能在实例作用于范围内使用。

全局注册:

Vue.component('my-component', {
    template : '<div>这是组件中的内容</div>'
});

 或者使用局部注册:

var myTemplateContent = {
   template : '<div>这是组件中的内容</div>'
};

new Vue({
   el : '#app',
   components : {
      'my-component' : this.myTemplateContent
   }
});

 使用组件:

<div id="app">
   <my-component></my-component>
</div>
→ 组件自定义标签名使用全小写加-符号的格式(如上例中的my-component)
→ 组件template中的DOM结构必须仅被一个元素包围,例如上面的template : '<div>这是组件中的内容</div>'如果写成template : '这是组件中的内容'或者写成template : '<div>这是组件中的内容</div><div>这是组件另一个的内容</div>'会无法渲染或者渲染不全。
→ 自定义的组件标签在有些时候使用会受到限制,例如:如果直接在<table>标签中使用<my-component>标签是无效的,解决方法是使用is属性来挂载组件:
<div id="app">
    <table>
        <tbody is='my-component'></tbody>
    </table>
</div>
 二:组件的选项
组件和Vue实例一样,也可以有data,computed,methods选项,与Vue实例不同的是,data选项必须是一个函数,且将数据return出去(至于为什么要这样做,是因为组件是可以复用的,每个组件实例的数据应该互相独立互不影响,使用return返回一个数据对象可以保证每个组件的实例的数据都是独立的)。
<body>
 <div id="app">
 <my-component></my-component>
 </div>
 <script>
    var myTemplateContent = {
        template : '<div>{{message}}</div>',
        data : function(){
            return {message : '这是组件中的内容'}
        }
    };


    new Vue({
        el : '#app',
        components : {
            'my-component' : this.myTemplateContent
        }
    })
 </script>
</body>

三:使用props在组件之间传递数据

组件可以进行层级嵌套,父组件的data是不能直接被子组件访问的,需要通过props参数来传递数据,传递的值可以是一个字符串数组或者对象。

使用字符串数组传递数据:
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <my-component message="来自父组件的数据"></my-component>
11         </div>
12 
13         <script>
14 
15             var myTemplateContent = {
16                 props : ['message'],
17                 template : '<div>{{message}}</div>'
18             };
19 
20             new Vue({
21                 el : '#app',
22                 components : {
23                     'my-component' : this.myTemplateContent
24                 }
25             })
26         </script>
27     </body>
28 </html>
View Code

在上述示例中,<my-component message="来自父组件的数据"></my-component>的message属性值可以是v-bind动态绑定的数据,当绑定的数据更新时,模板内容也会动态更新:

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Test page</title>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <input type="text" v-model='inputValue'>
            <my-component :message='inputValue'></my-component>
        </div>

        <script>

            var myTemplateContent = {
                props : ['message'],
                template : '<div>{{message}}</div>'
            };

            new Vue({
                el : '#app',
                components : {
                    'my-component' : this.myTemplateContent
                },
                data : {
                    inputValue : ''
                }
            })
        </script>
    </body>
</html>
View Code

 上述代码示例中,当在input输入框中输入值时,会更新data中的inputValue,因此my-component组件的message属性值也会动态更新,组件的内容也随之动态更新。

组件之间除了可以进行数据通信,还可以进行组件之间的事件调用,从而完成消息发送和接收。

四:使用自定义事件从子组件向父组件传递数据

类似观察者模式,在Vue中,子组件使用$emit()来触发事件,父组件使用$on()来监听子组件的事件。

在下面的例子中,子组件my-component有两个按钮,handleIncrease和handleReduce分别用于增加和减少模板的data组件的counter值,然后使用$emit()方法通知父组件的increase和reduce方法。$emit()方法的第一个参数是自定义事件的名称,后续参数是要传递的数据。父组件使用handleGetTotal方法将接收到的参数赋值给自身的total值上。

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <title>Test page</title>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    <body>
        <div id="app">
            <p>总数:{{total}}</p>
            <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
        </div>

        <script>

            var myTemplateContent = {
                template : '\
                <div>\
                    <button @click="handleIncrease">+1</button>\
                    <button @click="handleReduce">-1</button>\
                </div>',
                data : function (){
                    return {counter : 0}
                },
                methods : {
                    handleIncrease : function(){
                        this.counter++;
                        this.$emit('increase', this.counter);
                    },
                    handleReduce : function(){
                        this.counter--;
                        this.$emit('reduce', this.counter);
                    }
                }
            };

            new Vue({
                el : '#app',
                components : {
                    'my-component' : this.myTemplateContent
                },
                data : {total : 0},
                methods : {
                    handleGetTotal : function(total){
                        this.total = total;
                    }
                }
            })
        </script>
    </body>
</html>
View Code
五:非父子组件之间的通信——中央事件总线bus
在Vue.js 1.x版本中,使用$dispath()和$broadcast()两个方法来向上级或者下级派发广播事件,这两种方法发出的事件,任何组件都是可以接收到且根据就近原则,事件会在第一次被接收到后停止冒泡,除非处理事件的方法又再一次返回true。
在Vue.js 2.x版本中,使用一个空的Vue对象作为中央事件总线,从而实现了任何组件之间的通信,包括父子组件、兄弟组件、跨级别组件,示例代码:
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <p>{{message}}</p>
11             <my-component ></my-component>
12         </div>
13 
14         <script>
15             //空的Vue对象作为"中介"
16             var bus = new Vue();
17 
18             var myTemplateContent = {
19                 template : '<button @click="handleEvent">传递事件</button>',
20                 methods : {
21                     handleEvent : function(){
22                         bus.$emit('on-message', '来自my-component组件中的内容')
23                     }
24                 }
25             };
26 
27             new Vue({
28                 el : '#app',
29                 components : {
30                     'my-component' : this.myTemplateContent
31                 },
32                 data : {message : ''},
33                 mounted : function(){
34                     var that = this;
35                     //Vue实例初始化时,监听来自bus的事件
36                     bus.$on('on-message', function(msg){
37                         that.message = msg;
38                     })
39                 }
40             })
41         </script>
42     </body>
43 </html>
View Code
上面的代码,首先创建一个名为bus的空Vue实例,然后定义组件my-component,这个组件中有个按钮,按钮会触发bus组件的on-message方法,在app初始化mounted阶段监听bus的on-message事件,并在回调中完成自己的业务逻辑。
除了使用中央事件总线,还可以使用父链或者子组件索引两种方式来完成组件通信。

六:组件之间的通信——父链和子组件索引

在子组件中,可以使用this.$parent来直接访问组件的父组件,父组件也可以用this.$children来访问它的所有子组件,且可以向上/向下无线递归访问,直到实例根元素或者最内层元素。实例代码:

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <p>{{message}}</p>
11             <my-component ></my-component>
12         </div>
13 
14         <script>
15             //空的Vue对象作为"中介"
16             var bus = new Vue();
17 
18             var myTemplateContent = {
19                 template : '<button @click="handleEvent">通过父链直接修改数据</button>',
20                 methods : {
21                     handleEvent : function(){
22                         this.$parent.message = '来自组件my-component的内容';
23                     }
24                 }
25             };
26 
27             new Vue({
28                 el : '#app',
29                 components : {
30                     'my-component' : this.myTemplateContent
31                 },
32                 data : {message : ''}
33             })
34         </script>
35     </body>
36 </html>
View Code
建议的做法,父子组件之间还是使用props和emit来传递数据,子组件应避免直接修改父组件的数据。
在父组件中,如果通过使用this.$children得到子组件,然后通过遍历获取所需要的子组件是比较困难的,可以使用ref属性来为子组件指定索引名称,这样可以通过this.$refs.myRef来指定访问myRef这个子组件。
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <button @click="handleRef">通过ref获取子组件实例</button>
11             <my-component ref="comMy"></my-component>
12         </div>
13 
14         <script>
15             var myTemplateContent = {
16                 template : '<div>子组件</div>',
17                 data : function(){
18                     return {
19                         message : '子组件内容'
20                     }
21                 }
22             };
23 
24             new Vue({
25                 el : '#app',
26                 components : {
27                     'my-component' : this.myTemplateContent
28                 },
29                 methods : {
30                     handleRef : function(){
31                         //通过refs访问指定的子组件实例
32                         var msg = this.$refs.comMy.message;
33                         console.log(msg)
34                     }
35                 }
36             })
37         </script>
38     </body>
39 </html>
View Code

$refs只在组件渲染完成后填充,并不是响应式的,应避免在模板和计算属性中使用$refs。

七:使用slot插槽分发内容
  任何一个复杂的Vue组件都是由props传递数据、events触发事件和slot内容分发这三个部分构成的。当需要组合使用不同的组件,混合父组件的内容和子组件的模板时,就需要使用slot,这个过程叫内容分发。
  关于父子组件的作用域:父组件模板的内容在父组件作用域中编译,子组件模板的内容在子组件作用域中编译。
  slot分发的内容,作用域是在父组件上的,如下面的示例说明: 
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <!--这里的showChild绑定的是父组件的数据-->
11             <my-component v-show="showChild"></my-component>
12         </div>
13         <script>
14             var myTemplateContent = {
15                 template : '<div>子组件</div>'
16             };
17             new Vue({
18                 el : '#app',
19                 components : {
20                     'my-component' : this.myTemplateContent
21                 },
22                 data : {
23                     showChild : true
24                 }
25             })
26         </script>
27 
28         <div id="app2">
29             <my-component ></my-component>
30         </div>
31         <script>
32             var myTemplateContent = {
33                 //这里的showChild绑定的是子组件的数据
34                 template : '<div v-show="showChild">子组件</div>',
35                 data : function(){
36                     return {
37                         showChild : true
38                     }
39                 }
40             };
41             new Vue({
42                 el : '#app2',
43                 components : {
44                     'my-component' : this.myTemplateContent
45                 }
46             })
47         </script>
48     </body>
49 </html>
View Code

(一)单个Slot插槽的用法 

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <child-component>
11                 <p>分发的内容</p>
12                 <p>更多分发的内容</p>
13             </child-component>
14         </div>
15         <script>
16             var myTemplateContent = {
17                 template : '\
18                 <div>\
19                     <slot>\
20                         <p>如果父组件没有插入内容,此行文字将作为默认内容</p>\
21                     </slot>\
22                 </div>'
23             };
24             new Vue({
25                 el : '#app',
26                 components : {
27                     'child-component' : this.myTemplateContent
28                 }
29             })
30         </script>
31     </body>
32 </html>
View Code
(二)具名插槽的用法
给slot指定name属性可以分发多个内容:
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <child-component>
11                 <h2 slot="header">标题</h2>
12                 <p>分发的内容</p>
13                 <p>更多分发的内容</p>
14                 <div slot="footer">底部信息</div>
15             </child-component>
16         </div>
17         <script>
18             var myTemplateContent = {
19                 template : '\
20                 <div class="container">\
21                     <div class="header">\
22                         <slot name="header"></slot>\
23                     </div>\
24                     <div class="main">\
25                         <slot></slot>\
26                     </div>\
27                     <div class="footer">\
28                         <slot name="footer"></slot>\
29                     </div>\
30                 </div>'
31             };
32             new Vue({
33                 el : '#app',
34                 components : {
35                     'child-component' : this.myTemplateContent
36                 }
37             })
38         </script>
39     </body>
40 </html>
View Code
(三)作用域插槽的用法
作用域插槽使用一个(可复用的)模板来代替已渲染的元素,示例:
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <child-component>
11                 <template scope="props">
12                     <p>来自父组件的内容</p>
13                     <p>{{props.msg}}</p>
14                 </template>
15             </child-component>
16         </div>
17         <script>
18             var myTemplateContent = {
19                 template : '\
20                 <div class="container">\
21                     <slot msg="来自子组件的内容"></slot>\
22                 </div>'
23             };
24             new Vue({
25                 el : '#app',
26                 components : {
27                     'child-component' : this.myTemplateContent
28                 }
29             })
30         </script>
31     </body>
32 </html>
View Code

子组件的模板中,<slot msg="来自子组件的内容"></slot>向插槽传递了一个msg,父组件使用了template元素,且有一个scope="props"的属性,然后在template内部就可以使用props.msg来访问子组件传递过来的数据了。(注意到这里的scope="props"中的props只是一个临时变量,可以为任意名字)

(四)访问slot
在Vue.js 2.x中,可以使用$slots来访问被slot分发的内容。
 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <meta charset=utf-8>
 5         <title>Test page</title>
 6         <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 7     </head>
 8     <body>
 9         <div id="app">
10             <child-component>
11                 <h2 slot="header">标题</h2>
12                 <p>分发的内容</p>
13                 <p>更多分发的内容</p>
14                 <div slot="footer">底部信息</div>
15             </child-component>
16         </div>
17         <script>
18             var myTemplateContent = {
19                 template : '\
20                 <div class="container">\
21                     <div class="header">\
22                         <slot name="header"></slot>\
23                     </div>\
24                     <div class="main">\
25                         <slot></slot>\
26                     </div>\
27                     <div class="footer">\
28                         <slot name="footer"></slot>\
29                     </div>\
30                 </div>',
31                 mounted : function(){
32                     var header = this.$slots.header;
33                     var main = this.$slots.default;
34                     var footer = this.$slots.footer;
35                     console.log({
36                         header,
37                         main,
38                         footer
39                     })
40                 }
41             };
42             new Vue({
43                 el : '#app',
44                 components : {
45                     'child-component' : this.myTemplateContent
46                 }
47             })
48         </script>
49     </body>
50 </html>
View Code

 

posted on 2020-02-16 16:41  Rumble Zheng  阅读(395)  评论(0编辑  收藏  举报