Vue.js实战读书笔记--组件详情

11 篇文章 0 订阅

第7章  组件详情

7.1 组件与复用 

7.1.1  为什么使用组件

         组件的作用是为了代码可复用,提高重用性。在使用组件时可以自定义标签。比如:<Card>、<Row>、<i-col>等。

7.1.2  组件用法

         组件需要注册后才能使用。注册分为全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用。组件名自定义,最好使用小写加减号的形式命名。要是在父实例使用组件,必须要在实例创建前注册。

<div id="app">
         <my-component></my-component>
</div>
<script>
         Vue.component('my-component',{
                   //选项,需要显示的组件内容
                   template:'<div>组件内容</div>';
         });  
         var app = new Vue({
                   el:'#app',
         })
</script>

Template的DOM结构必须被一个元素包含,如果不带<div></div>标签是无法渲染的。

Vue实例中,使用component选项可以局部注册组件,注册的组件只在该Vue实例下有效。

<div id="app">
      <my-component></my-component>
</div>
<script>
      var Child = {
           template:'<div>组件内容</div>'
      }
      var app = new Vue({
           el:'#app',
           components:{
               'my-component':Child
           }
      })
</script>

     Vue组件的模板受Html标签的限制,如<table>只允许<td>、<tr>、<th>等表格元素。所以直接在<table>内使用组件时无效的。这时,要使用特殊的is属性来挂载组件。

<div id="app">
                              <table>
                                     <tbody is="my-component"></tbody>
                              </table>
</div>

提示:

     除template选项外,还可以使用其他的选项,比如:data、computed、methods等。但在使用data时,和实例的区别是,data必须是函数,然后将数据return出去。

<div id="app">
                              <my-component></my-component>
</div>
<script>
                              Vue.component('my-component',{
                                     //选项,需要显示的组件内容
                                     template:'<div>{{message}}/div>',
                                     data:function(){
                                               return {
                                                        message:'组件内容'
                                               }
                                     }
                              });
                              var app = new Vue({
                                     el:'#app',
                              })
</script>

注:javascript对象是引用关系,所以如果return出的对象引用了外部的一个对象,那这个对象就是共享的,任意一方修改皆会改变。

<div id="app">
                              <my-component></my-component>
                              <my-component></my-component>
                              <my-component></my-component>
</div>
<script>
                              var data={
                                     counter:0
                              };
                              Vue.component('my-component',{
                                     //选项,需要显示的组件内容
                                     template:'<button @click="counter++">{{counter}}</button>',
                                     data:function(){
                                               return data;
                                     }
                              });
                              var app = new Vue({
                                     el:'#app',
                              })
</script>

组件使用了三次,每次操作时counter值都会改变,因为data引用了外部对象,可以用以下做法实现独立控制。

<div id="app">
                              <my-component></my-component>
                              <my-component></my-component>
                              <my-component></my-component>
</div>
<script>

                              Vue.component('my-component',{
                                     //选项,需要显示的组件内容
                                     template:'<button @click="counter++">{{counter}}</button>',
                                     data:function(){
                                               var data={
                                                        counter:0
                                               };
                                     }
                              });
                              var app = new Vue({
                                     el:'#app',
                              })
</script>

7.2 使用props传递数据

7.2.1  基本用法

         组件不仅仅要把模板内容复用,也需要实现组件间的通信。通常父组件会向子组件传递数据或参数,子组件通过获取父组件的数值不同渲染不同效果。这个正向传递就是通过props实现的。使用props声明需要从父组件中接收的数据,props可以分为字符串数组和对象两种。

<div id="app">
                              <my-component message="来自父组件的数据"></my-component>
</div>
<script>
                              Vue.component('my-component',{
                                     props:{'message'},
                                     template:'<div>{{message}}</div>',
                              });
                              var app = new Vue({
                                     el:'#app',
                              })
</script>

      Props中声明的数据和组件data函数return的数据主要区别在于props来自父级,而data中是组件自己的数据,作用域是组件本身。这两种数据都可以在模板template及计算属性computed和方法methods中使用。

      由于html不区分大小写,当时使用DOM模板时,驼峰命名会被转化为短横分割命名。

      当传递的值是动态赋值时,需要使用v-bind来绑定props值,当父组件数值变化时,子组件也会相应改变。

<div id="app">
                              <input type="text" v-model="parentMessage">
                              <my-component :message="parentMessage"></my-component>
</div>
<script>
                              Vue.component('my-component',{
                                     props:['message'],
                                     template:'<div>{{message}}</div>',
                              });
                              var app = new Vue({
                                     el:'#app',
                                     data:{
                                               parentMessage:''
                                     }
                              })
</script>

7.2.2  单向数据流

         Vue2.X通过props传递数据是单向的,父组件数值变化可以传递到子组件,但反向则不行。业务中经常会有两种要改变prop的情况。一种是父组件传递初始值进来,子组件将初始值保存起来,在自己的作用域随意修改。这种需要再组件data中声明数据,引用父组件的prop。

<div id="app">
                              <my-component :init-count="1"></my-component>
</div>
<script>
                              Vue.component('my-component',{
                                     props:['initCount'],
                                     template:'<div>{{count}}</div>',
                                     data:function(){
                                               return{
                                                        count:this.initCount
                                               }
                                     }
                              });
                              var app = new Vue({
                                     el:'#app'
                              })
</script>

还有种情况是prop作为需要被转变的原始值传入,这时需要使用计算属性。

<div id="app">
                              <my-component :width="100"></my-component>
</div>
<script>
                              Vue.component('my-component',{
                                     props:['width'],
                                     template:'<div :style="style">组件内容</div>',
                                     computed:{
                                               style:function(){
                                                        return{
                                                                 width:this.width + 'px'
                                                        }
                                               }
                                     }
                              });
                              var app = new Vue({
                                     el:'#app'
                              })
</script>

注意:在JavaScript中对象和数组是引用类型,指向同一个内存空间,所以当props是对象或者数组时,在子组件内改变是会影响到父组件的。

7.2.3  数据验证

         当需要验证时,可以使用对象写法实现,例如以下示例:

Vue.component('my-component',{
                   props:{
                            //数字类型
                            propA:Number,
                            //字符串或者数字类型
                            propB:[String,Number],
                            //布尔值,如果没有定义,默认true
                            propC:{
                                     type:Boolean,
                                     default:true,
                            },
                            //数字且必传
                            propD:{
                                     type:Number,
                                     required:true
                            },
                            //数组或对象,默认是一个函数返回
                            propE:{
                                     type:Array,
                                     default:function(){
                                               return [];
                                     }
                            },
                            //自定义验证函数
                            propF:{
                                     validator:function(value){
                                               return value > 10;
                                     }
                            }
                   },
});

7.3  组件通信

7.3.1   自定义事件

         当子组件向父组件传递数据时,要用到自定义事件。v-on除了监听DOM事件外,还可以用于组件间的自定义事件。子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。父组件也可以直接在子组件的自定义标签上使用v-on监听子组件触发的自定义事件。

 

7.3.2   使用v-model

         Vue2.X可以在自定义组件上使用v-model命令。

<div id="app">
         <p>总数:{{total}}</p>
         <my-component v-model="total"></my-component>
</div>
<script>
         Vue.component('my-component',{
                   template:'<button @click="handleClick">+1</button>',
                   data:function(){
                            return {
                                     counter:0
                            }
                   },
                   methods:{
                            handleClick:function(){
                                     this.counter ++ ;
                                     this.$emit('input',this.counter);
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
                   data:{
                            total:0
                   }
         })
</script>

v-model还可以创建自定义表单输入组件,进行数据双向绑定。

<div id="app">
         <p>总数:{{total}}</p>
         <my-component v-model="total"></my-component>
         <button @click="handleReduce"></button>
</div>
<script>
         Vue.component('my-component',{
                   props:['value'],
                   template:'<input :value="value" @input="updateValue">',
                   methods:{
                            updateValue:function(event){
                                     this.$emit('input',event.target.value);
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
                   data:{
                            total:0
                   },
                   methods:{
                            handleReduce:function(){
                                     this.total --;
                            }
                   }
         })
</script>

注意:实现上述的双向数据绑定v-model需要满足下面两个要求:

  1. 接收一个value属性;
  2. 有新的value时触发input事件。

 

7.3.3   非父子组件通信

         在实际业务中,除了父子组件之间需要通信外,还有兄弟组件和跨级组件之间。其中在Vue1.X中,除了$emit()外,还提供了$dispatch()和$broadcast(),其中$dispatch()是用于向上级派发事件,只要是它的父级,不论几级,都可以在实例的event选项中接收。$broadcast()是由上级向下级广播事件,用法一致,方向相反。

         在Vue2.X中废弃了上面两个方法,使用一个空的Vue实例作为中央事件总线(bus),类似一个中介的作用。

<div id="app">
         {{messagge}}
         <component-a></component-a>
</div>
<script>
         var bus = new Vue();
         Vue.component('component-a',{
                   template:'<button @click="handleEvent">传递事件</button>',
                   methods:{
                            handleEvent:function(){
                                     bus.$emit('on-messagge','来自组件component-a的内容');
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
                   data:{
                            messagge:''
                   },
                   mounted:function(){
                            var _this = this;
                            //在实例初始化时,监听bus实例的事件
                            bus.$on('on-messagge',function(msg){
                                     _this.messagge = msg;
                            });
                   }
         })
</script>

以上用法适用于任何组件之间的值传递。

除了中央事件总线bus外,还可以使用父链和子组件索引实现组件间的通信。

I、父链

在子组件中,使用this.$parent可以直接访问组件的父组件或实例,父组件也可以通过this.$children访问它所有的子组件。而且可以递归向上或向下无限访问,直到根实例或最内层的组件。但是在业务中,子组件尽量避免依赖父组件的数据,这样会造成父子耦合度过高,父子组件最好还是通过props和$emit来通信。

II、子组件索引

         当子组件数量较多时,通过this.$children遍历是非常困难,尤其当动态渲染时,它们的序列是不固定的。Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称。

<div id="app">
         <button @click="handleRef">通过ref获取子组件实例</button>
         <component-a ref="comA"></component-a>
</div>
<script>
         Vue.component('component-a',{
                   template:'<div>子组件</div>',
                   data:function(){
                            return {
                                     messagge:'子组件内容'
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
                   methods:{
                            handleRef:function(){
                                     //通过$refs访问指定的实例
                                     var msg = this.$refs.comA.messagge;
                                     console.log(msg);
                            }
                   }
         })
</script>

注意:$refs只在组件渲染完成后才填充,并且它是非响应式的。它仅仅作为一个直接访问子组件的应急方案,应当避免在模板或计算属性中使用$refs。

 

7.4  使用slot分发内容

7.4.1   什么是slot

         当需要组件组合使用时,混合父组件和子组件模板时,就会用到slot,这个过程就叫做内容分发。Props传递数据、events触发事件和slot分发内容构成了Vue组件的3个API来源。

7.4.2   作用域

         父组件模板的内容是在父组件作用域内编译,子组件模板内容在子组件作用域内编译。

<div id="app">
         <child-component v-show="showChild"></child-component>
</div>
<script>
         Vue.component('component-a',{
                   template:'<div>子组件</div>',
         });
         var app = new Vue({
                   el:'#app',
                   data:{
                            showChild:true
                   }
         })
</script>

这里showChild绑定的是父组件的数据,以下示例是在子组件上绑定:

<div id="app">
         <child-component></child-component>
</div>
<script>
         Vue.component('component-a',{
                   template:'<div v-show="showChild">子组件</div>',
                   data:function(){
                            return{
                                     showChild:true
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

7.4.3   slot用法

         单个slot

         在子组件内使用特殊<slot>元素可以为这个子组件开启一个slot(插槽),在父组件里插入在子组件标签内的所有内容姜替代子组件的<slot>标签及它的内容。

<div id="app">
         <child-component>
                   <p>分发内容</p>
                   <p>更多</p>
         </child-component>
</div>
<script>
         Vue.component(' child-component ',{
                   template:'\
                   <div>\
                            <slot>\
                                     <p>如果父组件没有插入内容,默认显示</p>\
                            </slot>\
                   </div>',
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

子组件child-component模板内定义一个<slot>元素,并且有个<p>作为默认内容,在父组件没使用slot时,会渲染默认文本,如果写入slot,会替换整个slot。

<div id="app">
         <div>
                   <p>分发内容</p>
                   <p>更多</p>
         </div>
</div>

         具名slot

         给<slot>元素指定name可以分发多个内容,具名slot可以和单个slot共用。

<div id="app">
         <child-component>
                   <h2 slot="header">标题</h2>
                   <p>分发内容</p>
                   <p>更多</p>
                   <div slot="footer">底部信息</div>
         </child-component>
</div>
<script>
         Vue.component('child-component',{
                   template:'\
                   <div class="container">\
                            <div class="header">\
                                     <slot name="header"></slot>\
                            </div>\
                            <div class="main">\
                                     <slot></slot>\
                            </div>\
                            <div class="footer">\
                                     <slot name="footer"></slot>\
                            </div>
                   </div>',
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

子组件中有3个slot,其中class=”main”的slot没有使用name特性,它将作为默认slot出现。

<div id="app">
         <div class="container">
                            <div class="header">
                                     <h2>标题</h2>
                            </div>
                            <div class="main">
                                     <p>分发内容</p>
                                     <p>更多</p>
                            </div>
                            <div class="footer">
                                     <div>底部信息</div>
                            </div>
                   </div>',
</div>

7.4.4   作用域插槽

         作用域插槽是一个特殊的slot,使用一个可以复用的模板替换已经渲染的元素。

<div id="app">
         <child-component>
                   <template>
                            <p>来自父组件的内容</p>
                            <p>{{props.msg}}</p>
                   </template>
         </child-component>
</div>
<script>
         Vue.component('child-component',{
                   template:'\
                   <div class="container">\
                            <slot msg="来自子组件的内容"></slot>\
                   </div>',
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

渲染后的结果:

<div id="app">
         <div class="container">
                   <p>来自父组件的内容</p>
                   <p>来自子组件的内容</p>
         </div>
</div>

 

7.4.5   访问slot

Vue2.X中提供了用来访问被slot分发内容的方法$slots。

mounted:function(){
         var header = this.$slots.header;
         var main = this.$slots.default;
         var footer = this.$slots.footer;
         console.log(footer);
         console.log(footer[0].elm.innerHTML);
}

 

7.5  组件高级用法

7.5.1   递归组件

         组件通过设置name的选项就可以递归的调用自己,如下:

<div id="app">
         <child-component :count="1"></child-component>
</div>
<script>
         Vue.component('child-component',{
                   name:'child-component',
                   props:{
                            count:{
                                     type:Number,
                                     default:1
                            }
                   },
                   template:'\
                   <div class="child">\
                            <child-component\
                            :count="count+1"\
                            v-if="count<3"></child-component>\
                            >
                   </div>',
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

 

7.5.2   内联模板

         组件模板一般都是在template选项内定义的,Vue提供了内联模板的功能,在使用时,给组件标签使用inline-template特性,组件就会把它内容当成模板,而不是把内容做分发。

<div id="app">
         <child-component inline-template>
                   <div>
                            <h2>在父组件中定义子组件的模板</h2>
                            <p>{{message}}</p>
                            <p>{{msg}}</p>
                   </div>
         </child-component>
</div>
<script>
         Vue.component('child-component',{
                   data:function(){
                            return {
                                     msg;'在子组件声明的数据'
                            }
                   }
         });
         var app = new Vue({
                   el:'#app',
                   data:{
                            message:'在父组件声明的数据'
                   }
         })
</script>

 

7.5.3   动态组件

         Vue.js提供特殊元素<component>用来动态挂载不同的组件,使用is特性来选择挂载的组件。

<div id="app">
         <component :is="currentView"></component>
         <button @click="handleChangeView('A')">切换到A</button>
         <button @click="handleChangeView('B')">切换到B</button>
         <button @click="handleChangeView('C')">切换到C</button>
</div>
<script>
         var app = new Vue({
                   el:'#app',
                   components:{
                            comA:{
                                     template:'<div>组件A</div>'
                            },
                            comB:{
                                     template:'<div>组件B</div>'
                            },
                            comC:{
                                     template:'<div>组件C</div>'
                            },
                   },
                   data:{
                            currentView:'comA',
                   },
                   methods:{
                            handleChangeView:function(component){
                                     this.currentView = 'com'+component;
                            }
                   }
         })
</script>

7.5.4   异步组件

         Vue.js允许将组件定义为一个工厂函数,动态解析组件,Vue.js只在组件需要渲染时触发工厂函数,并且把结果缓存起来。

<div id="app">
         <component></component>
</div>
<script>
         Vue.component('child-component',function(resolve,reject){
                   window.setTimeout(function(){
                            resolve({
                                     template:'<div>异步渲染</div>'
                            });
                   },2000);
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

 

7.6  其他

7.6.1   $nextTick

         异步更新队列:Vue观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生所有数据改变。在缓冲时会去除重复数据避免不必要的计算。在下一个事件循环tick中,Vue刷新队列并执行实际工作。

         当页面有v-if判断时,逻辑处理处理中会对相应的dom进行操作,但此时元素并未加载,页面就会报错,所以要使用$nextTick。$nextTick就是用来知道什么时候dom更新完成的。

7.6.2   X-Templates

         当组件内容过长时使用拼接字符串是很麻烦的。Vue提供了另一种定义模板的方式,在<script>标签中使用text/template类型,并指定id赋值template。这样在<script>中可以写html代码,不需要考虑换行问题了。

<div id="app">
         <my-component></my-component>
         <script type="text/x-template" id="my-component">
                  <div>这是组件的内容</div>
         </script>
</div>
<script>
         Vue.component('my-component',{
                   template:'#my-component'
         });
         var app = new Vue({
                   el:'#app',
         })
</script>

7.6.3   手动挂载实例

         动态创建Vue实例,Vue提供了Vue.extend和$mount两个方法手动挂载实例。如果实例在实例化没有收到el选项,它就处于“未挂载”状态。可以通过$mount手动挂载实例。<

div id="mount-div">
</div>
<script>
         var MyComponent = Vue.extend({
                   template:'<div>Hello:{{name}}</div>',
                   data:function(){
                            return{
                                     name:'world'
                            }
                   }
         });
         new MyComponent().$mount('#mount-div');
</script>

除了上述写法还有两种写法:

new MyComponent(
         el:'#mount-div'
});

//或者在文档之外渲染并且随后挂载

var component = new MyComponent().$mount();
document.getElementById('mount-div').appendChild(component.$el);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值