vue.js 精学组件记录

组件需要注册后才可以使用。

Vue.component('my-component',{

  template:'<div>这是组件内容</div>'  

});

局部注册组件

var Child = {
  template:'<div>局部注册组件内容</div>'  
}

var app = new Vue({
   el:"#app",
   components: {
       'my-component':Child 
    }  
})    

Vue组件模板在某些情况下会受到限制,比如table,ul,ol.select等,这时可以类似这样:

<table>
    <tbody is="my-component"></tbody>
</table>

还可使用数据绑定,但data必须是函数:

Vue.component('my-component',{
     template:'<div>{{message}}</div>',
     data:function(){
         return {
             message:'局部注册内容,data方法return'
         }
     }
})

使用props传递数据

<my-component message="来自父组件的信息"></my-component>
Vue.component('my-component',{
       props:['message'],
       template:'<div>{{message}}</div>'
})

父组件动态数据:

<input type="text" v-model="message"/>
<my-component :message="message"></my-component>
Vue.component('my-component',{
       props:['message'],
       template:'<div>{{message}}</div>'
})


var app = new Vue({
     el:"#app",
     data:{
          message:''
     }

})

当输入框输入时,子组件接收到的props "message" 也会实时响应,并更新组件模板。

注意:

如果直接传递数字,布尔值,数组,对象,而不是用v-bind,传递的是字符串。

<my-component message="[1,2,3]"></my-component>
<my-component :message="[1,2,3]"></my-component>
Vue.component('my-component',{
       props:['message'],
       template:'<div>{{message.length}}</div>'
})

此时上一个会输出字符串的长度7,下面才是输出数组的长度3。

单向数据流

父组件传递初始值进来,子组件作为初始值保存,在自己的作用域下可随意修改。

<div id="app">
        <my-component :init-count="1"></my-component>
    </div>


    <script type="text/javascript">

        Vue.component('my-component',{
            props:['initCount'],
            template:'<div>{{count}}</div>',
            data:function(){
                return {
                    count:this.initCount+1
                }
            }
        })


        var app = new Vue({
            el:"#app",
            data:{
                message:''
            }

        })
    </script>
<div id="app">
        <my-component :width="100"></my-component>
    </div>


    <script type="text/javascript">

        Vue.component('my-component',{
            props:['width'],
            template:'<div :style="style">组件内容</div>',
            computed:{
                style:function(){
                    return {
                        width:this.width+"px",
                        background:'red'
                    }
                }
            }
        })


        var app = new Vue({
            el:"#app",
            data:{
                message:''
            }

        })
    </script>

子组件传递数值到父组件:

<div id="app">
        <p>总数:{{total}}</p>
        <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',{
            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);
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                total:0
            },
            methods:{
                handleGetTotal:function(total){
                    this.total=total
                    console.log(total)
                }
            }

        });

在子组件定义两个事件,改变自身数据的同时使用$emit来触发父组件的自定义事件名称,从而触发对应的方法,再把改变的数据通过方法传到父组件的方法,从而改变父组件的数据。

使用v-model

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

        });
    </script>

这次区别是$emit通知的是特殊的input,但是并没有在组件上使用@input,这是因为v-model,这也可以称作是一个语法糖。

因为上面的示例可以间接实现:

<my-component @input="total"></my-component>

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

<div id="app">
        <p>总数:{{total}}</p>
        <my-component v-model="total"></my-component>
        <button @click="handleReduce">-1</button>
    </div>
    <script type="text/javascript">
        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>

子组件input改变value值后出发updateValue,通过$emit触发特殊的input事件,传值输入的value值,改掉父组件的total,父组件的handleReduce方法改变total后,由于是双向绑定,所以子组件的value值也随着改变。

这需要满足两个条件:

1.接收一个props的value值,

2.更新value触发特殊的input事件

非父子组件通信

在vue1.x中是采用$dispatch()和$broadcast()这两个方法。$dispatch()由于向上级派发事件,只要是它的父级,都可在vue实例的events选项内接收。

在vue2.x中废弃了上述两种方法。在vue2.x中推荐使用一个空的vue实例作为中央事件总线(bus),也就是一个中介。

如下:

<div id="app">
        <p>信息:{{message}}</p>
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        var bus = new Vue();
        Vue.component('my-component',{
            template:'<button @click="handleEvent"></button>',
            methods:{
                handleEvent:function(event){
                    bus.$emit('on-message','来自组件component的内容');
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                message:''
            },
            mounted:function(){
                var _this = this;
                bus.$on('on-message',function(msg){
                    _this.message=msg;
                 })
            }

        });
    </script>

定义一个空的vue实例当做中间人,在钩子函数mounted中监听了来自bus的事件on-message,在组件中会点击按钮通过bus把事件on-message发出去,此时app会接收到来自bus的事件。

除了中间介这种方式外,还有两种方法可实现组件间的通信:父链和子组件索引。

父链

子组件中使用this.$parent可直接访问父实例或组件,父组件也可以通过this.$children访问它所有子组件。

<div id="app">
        <p>信息:{{message}}</p>
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',{
            template:'<button @click="handleEvent">通过$parent改变信息内容</button>',
            methods:{
                handleEvent:function(event){
                    this.$parent.message='子组件通过$parent改变了信息内容';
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                message:''
            }

        });
    </script>

子组件内通过this.$parent.message直接修改信息内容,正常使用不推荐使用。因为这样使得父子组件紧耦合,只看父组件,很难理解父组件状态,因为它可能被任意组件修改。最好还是通过props和$emit来通信

子组件索引

<div id="app">
        <button @click="handleRef">通过red获取子组件实例</button>
        <my-component ref="Mycom"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',{
            template:'<div>子组件</div>',
            data:function() {
                return {
                    message:'子组件内容'
                }
            }
        });
        var app = new Vue({
            el:"#app",
            methods:{
                handleRef:function(){
                    var msg = this.$refs.Mycom.message;
                    console.log(msg)
                }
            }

        });
    </script>

父组件内的内容通过this.$refs.Mycom找到子组件,并输出了子组件的内容。

使用slot分发内容

当需要让组件组合使用,混合父组件发的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。

slot用法

单个slot: 在子组件标签内的素有内容替代子组件的<slot>标签及它的内容。如下:

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

当父组件没有插入分发的内容,即上述代码没有

<p>分发的内容</p>

<p>跟多分发的内容</p>

的时候,子组件将默认显示子组件里slot的内容,如果父组件里插入了分发的内容,则将替换掉子组件里slot标签里的全部内容。

具名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 type="text/javascript">
        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>

在父组件上面可以具名插入slot的name,然后子组件会更具name插入具体的内容。

作用域插槽

是一种特殊的slot使用一个可以复用的模板替换已渲染的元素。

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

子组件模板,在slot元素上有一个类似pros传递数据给组件的写法 msg="XXX",将数据传到了插槽。父组件使用了template且拥有一个scope="props"的特性,这里的props知识一个临时变量,通过临时变量props来访问来自子组件的数据msg。渲染的最终结果为:

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

作用域插槽具代表性的用例是列表组件,允许组件自定义应该如何渲染列表的每一页。

<div id="app">
        <my-list :book="books">
            <!--作用域插槽也可以是具名的slot-->
            <template slot="book" scope="props">
                <li>{{props.bookName}}</li>
            </template>
        </my-list>
    </div>
    <script type="text/javascript">
        Vue.component('my-list',{
            props:{
                books:{
                    type:Array,
                    default:function(){
                        return [];
                    }
                }
            },
            template:'<ul><slot name="book" v-for="book in books" :bookName="book.name"></slot></ul>',
        });
        var app = new Vue({
            el:"#app",
            data:{
                books:[
                    {name:'js'},
                    {name:'css'},
                    {name:'html'}
                ]


            }
        });
    </script>

子组件my-list接收一个来自父级的prop数组books,并且将它在name为book的slot上使用v-for指令循环,同时暴露一个变量bookName。

访问slot

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

<div id="app">
        <my-component>
            <h2 slot="header">标题</h2>
            <p>正文内容</p>
            <div slot="footer">底部信息</div>
        </my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-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>`,
            mounted:function(){
                var header = this.$slots.header;
                var main = this.$slots.default;
                var footer = this.$slots.footer;

                console.log(footer[0].elm.innerHTML)
            }
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

在子组件钩子函数中能用$slots得到对应的slot。

组件的高级用法

递归组件

组件可以在它的模板内调用自己,只要给组件设置name属性就可以。

<div id="app">
        <my-component :count="1"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',{
            name:'my-component',
            props:{
                count:{
                    type:Number,
                    default:function(){
                        return 1;
                    }
                }
            },
            template:'<div class="child"><my-component :count="count+1" v-if="count<3"></my-component></div>',


        });
        var app = new Vue({
            el:"#app",
        });
    </script>

设置name后就可以递归使用了,不过需要一个限制条件,不然会报错。

内联模板

vue提供了一个内敛模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当做模板,而不是把它当内容分发。

<div id="app">
        <my-component inline-template>
            <div>
                <h2>父组件定义子组件模板</h2>
                <p>{{message}}</p>
                <p>{{msg}}</p>
            </div>
        </my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',{
            data:function(){
                return {
                    msg:'子组件内容'
                }
            }


        });
        var app = new Vue({
            el:"#app",
            data:{
                message:'父组件内容'
            }
        });
    </script>

渲染成:

<div>
   <h2>父组件定义子组件模板</h2>
   <p>父组件内容</p>
   <p>子组件内容</p>
</div>

使用内联模板会导致作用域混淆,应避免使用。

动态组件

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 type="text/javascript">
        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>

动态地改变currentView就可以切换视图。

异步组件

当工程足够大,使用组件足够多时,是时候考虑下性能了,因为一开始把所有组件都加载时时一笔开销。vue.js允许将组件定义为一个工厂函数,动态地解析组件。

<div id="app">
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        Vue.component('my-component',function(resolve,reject){
            setTimeout(()=>{
                resolve({
                    template:'<div>我是异步渲染的</div>'
                })
            },2000)
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

$nextTick

当v-if,动态切换显示时,直接去取div的内容时获取不到的,因为此时div还没有被创建出来。这里涉及到一个vue的重要概念:异步更新队列。

vue在观察到数据变化时并不是直接更新DOM,而是开启一个队列,并缓冲在同一事件循环中发生的数据变化。

$nextTick就是用来知道DOM什么时候更新好的。

X-template

没有使用webpack,gulp等工具,组件内容复杂,如果都在javascript中拼接字符串,效率很低,vue提供了另一种定义模板的方式,在script标签使用text/template类型,指定一个ID,将这个id赋给template。

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

vue的初衷不是滥用它,因为它将模板和组件的其他定义隔离了。

猜你喜欢

转载自www.cnblogs.com/liuzhixiang/p/8971965.html