第七章:vue.js组件详解Ⅰ(基础篇)

1字数 7197阅读 1817

      组件(Component)是Vue.js最核心的功能,也是整个架构设计最精彩的地方,当然也是最难掌握的。本章将带领你由浅入深地学习组件的全部内容,并通过几个实战项目熟练使用Vue组件。

7.1组件与复用

7.1.1为什么使用组件   

        组建可以提高一些控件、JavaScript能力的复用,提高组件重用性,让代码可复用。我们先写一个聊天页面的,示例代码如下:

<Card style="width:350px">

    <p slot="title">与XXX聊天</p>

    <a href="#" slot="extra">

        <Icon type="android-close" size="18"></Icon>

    </a>

    <div style="height:100px"></div>

    <div>

        <Row :gutter="16">

            <i-col span="17">

                <i-input v-model="value" placeholder="请输入……"</i-input>

            </i-col>

            <i-col span="4">

                <i-button type="primary" icon="paper-airplane"发送</i-button>

            </i-col>

        </Row>

    </div>

</Card>

        是不是很奇怪,有很多我们从未使用过的标签,比如<Card>、<Row>、<i-col>、<i-input>、<i-button>等,而且整段代码除了内联的几个样式外,一句CSS代码也没有。

        这些没见过的自定义标签就是组件,每个标签代表一个组件,在任何时候使用Vue的地方都可以直接使用。接下来,我们就来看看组件的具体用法。

7.1.2组建用法

        回顾创建Vue示例的方法,我们发现组件与之类似,需要注册后才可以使用。注册有全局注册局部注册两种方式,全局注册后,任何Vue实例都可以使用,全局注册示例代码如下:

        Vue.component('my-component',{

            //my-component就是注册的组件自定义标签名称,推荐使用小写加减号分隔符的形式命名

    });

        要在父实例中使用这个组件,必须要在实例创建前注册,之后就可以用<my-component>来使用组件,实例代码如下:

    <div id="app">

        <my-component></my-component >

    <div>

    <script>

      Vue.component("my-component",{

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

//template的DOM结构必须被一个元素包含,如果直接写成“这里是组件的内容”,不带"<div></div>是无法被渲染的“

    });

    var app = new Vue({

        el:'#app'

    })

<script>

        在Vue实例中,使用components选项可以局部注册组件,注册后的组件只有在该实例作用域下有效。组件中也可也使用components选项来注册组件,使组件可以嵌套。示例代码如下:

    <div id="app">

        <my-component></my-component>

    </div>

    <script>

      var Child={

        template:'<div>局部注册组件的内容</div>

    }

        var app = new Vue({

        el:'#app',

        compoments:{

            'my-component':Child

        }

    });

        Vue组件的模板在某些情况下会受到HTML的影响,比如<table>内规定只允许是<tr>、<td>、<th>等这些表格元素,所以在<table>内直接使用组件是无效的。这种情况下,可以使用特殊的属性来挂载组件,示例代码如下:

    <div id="app>>

        <table>

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

        </table>

    </div>

    <script>

        Vue.componnt("my-compone",{

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

    });

        var app =new Vue({

            el:'#app'

    })

    </script>

    tips:如果使用的是字符串模板,是不受限制的,比如后面章节介绍的.VUE单文件用法等。

    除了template选项外,组件中还可以像Vue实例那样使用其它的选项,比如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:'组件内容'

        }

    }

});

……

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}}</template>

        data:function(){

            return data;

        }

    });

    var app = new Vue({

        el:'#app'

    })

    组件使用了三次,但是点击任意一个<button>,3个数字都会加1,那是因为组件的data引入的是外部的对象,这肯定不是我们期望的结果,所以给组件返回一个新的data对象来独立,示例代码如下:

……

<script>

    Vue.component('my-component',{

    template:'<button @click="counter++">{{counter}}</button>',

    data:function(){

        return {

            counter:0

            }

        }

    });

……

这样,点击三个按钮就互不影响了,完全达到复用的目的。


7.2使用props传递数据

7.2.1基本用法

        组件不仅仅是要把模板 的内容进行复用,更重要的是组件间要进行通信。通常父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件接收到后根据参数的不同来渲染不同的内容或渲染操作。这个正向传递数据的过程就是通过props来实现的。

        在组件中,使用选项props来声明需要从父级接受的数据,props的值可以是两种,一种是字符串数组,一种是对象,本小节先介绍数组的用法。比如我们构造一个数组,接收一个来自父级的数据message,并把它在组件模板中渲染,示例代码如下:

<div id="app">

    <my-component message="来自父组件的数据"></my-component>

</div>

<script>

    Vue.component.('my-compnent',{

        pros:['message'],

        template:'<div>{{message}}</div>'

});

var app = new Vue({

        el:'#app'

})

渲染后的结果为:

<div id="app">

    <div>来自父组件的数据</div> 

</div>

        props中声明的数据与组件data函数return的数据主要区别就是props的来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。上例的数据message就是通过props从父级传递过来的,在组件的自定义便签上直接写该props的名称,如果要传递多个数据,在props数组中添加项即可。

        由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名(camelCase)的props名称要转为短横分割命名(kebab-case),例如:

<div id="app">

    <my-component warning-text="提示信息"></my-component>

</div>

<script>

    Vue.Component('my-component',{

    props:['warningText'],

    template:'<div>{{warningText}}</div>'

});

    var app = new Vue({

        el:'#app'

})

</script>

        有时候,传递的数据并不是直接写死的,而是来自父级的动态数据,这时可以使用命令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>

        这里用v-model绑定了父级的数据parentMessage,当通过输入框任意输入时,子组件接收到的props:"message"也会实时响应,并更新组件模板。

注意:如果你要直接传递数字、布尔值、数组、对象,而且不使用v-bind,传递的仅仅是字符串,尝试下面的示例来对比:

<div id="app">

        <my-component message="[1,2,3]"></my-component>

        <my-component :message="[1,2,3]"></my-component>

</div>

<script>

    Vue.component('my-component',{

        props:['message'],

        template:'<div>{{message.length}}</div>'

});

var app = new Vue({

        el:‘#app’

})

</script>

同一个组件使用了两次,区别仅仅是第二个使用的是v-bind,渲染后的结果,第一个是7,第二个才是数组的长度3.

7.2.2单向数据流

        Vue2.x与Vu1.x比较大的一个区别就是,Vue2.x通过props传递数据是单向的了,也就是父组件数据变化时会传递给子组件,但是反过来不行。而在Vue1.x里提供了.sync修饰符来支持双向绑定,之所以这样设计,是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态。

        业务中会经常遇到两种需要改变props的情况,一种是父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域下可以随意使用和修改,这种情况可以在组件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>

        组件中声明了数据count,它在组件初始化时会获取来自父组件的initCount,之后就与之无关了,只用维护count,这样就可以避免直接操作initCount。

        另一种情况就是prop作为需要被转变的原始值传入,这种情况使用计算属性就可以了,示例代码如下:

<div id="app">

        <my-component  :width="100"></my-componnet>

</div>

<script>

    Vue.component('my-component',{

        prosp:['width'],

        template:'<div :style="style">组件内容</div>',

        data:{

        style:function(){

            return :{

                    width:this.width+'px'

                }

            }

        }

    });

……

</script>

        因为用CSS传递宽度要带单位px,但是每次都写太麻烦,而且数值计算一般是不带单位的,所以统一在组件内使用计算属性就可以了。

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

7.2.3数据验证

        我们上面所介绍的props选项的值都是一个数组,一开始也介绍过,除了数组外,还可以是对象,当prop需要验证时,就需要对象写法。

        一般当你的组件需要提供给别人使用时,推荐都进行数据验证,比如某个数据必须是数字类型,如果传入字符串,就会在控制台弹出警告。

        以下是几个prop的示例:

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

            }

        }

    }

});

验证的type类型必须是:

  String

  Number

  Boolean

  Object

  Array

  Function

        type也可以是一个自定义构造器,使用instanceof检测。

        当prop验证失败时,在开发版本下会在控制台抛出一条警告。


7.3组件通信

        我们已经知道,从父组件向子组件通信,通过props传递数据就可以了,但Vue组件通信的场景不止有这一种,归纳起来,组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。本节将介绍各种组件之间通信的方法。

7.3.1自定义事件

        当子组件需要向父组件传递数据时,就要用到自定义事件。我们在介绍指令v-on时有提到,v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。

        如果你了解JavaScript的设计模式--观察者模式,一定知道dispatchEvent和addEventListener这两个方法。Vue组件也有与之类似的一套模式,子组件用$emit()来触发事件,父组件用$on()来监听子组件的事件。

        父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件,示例代码如下:

<div id="app">

        <p>总数:{{total}}</p>

        <my-component 

                    @increase="handleGetTotal" 

                    @reduce="handleGetTotal"></my-component>

</div>

<script>

    Vue.component('my-component',{

          template:'\

                <div>

                        <button @increase="handleIncrease">+1</button>

                        <button @reduce="handleReducee">-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;

        }

    }

})

</script>

        上面示例中,子组件有两个按钮,分别实现加一和减一的效果,在改变组件的data"counter"后,通过$.emit()再把它传递给父组件,父组件用v-on:increase和v-on:reduce,$emit()方法的第一个参数是自定义事件的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或填写多个。

        除了用v-on在组件上监听自定义事件,也可以监听DOM事件,这时可以用.native修饰符表示监听的是一个原生事件 ,监听的是该组件的根元素,示例代码如下:

<my-component v-on:click.native="handleClick"></my-component>

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>

        仍然是点击按钮加一的效果,不过这次组件$emit()的事件名是特殊的input,在使用组件的父级,并没有在<my-component>上使用@input="handler",而是直接用了v-model绑定一个数据total。这也可以称作一个语法糖,因为上面的示例可以间接地用自定义事件来实现:

……

<my-component @input="handleGetTotal|"></my-component>

……

var app =new Vue({

    el:'#app',

    data:{totoal:0},

    methods:{

        handleGetTotal:function(total){

        this.total=total;

        }

    }

})

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

<div id="app">

    <p>总数:{{total}}</p>

    <my-component v-model="total"></my-component>

    <button @click="handleReduce">-1</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组件要满足下面两个要求:

接收一个value属性

在有新的value时触发input事件。

7.3.3非父子组件通信

        在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种,兄弟组件和跨多级组件,为了更加彻底地了解Vue.js2.x中的通信方法,我们先来看一下在Vue.js1.x中是如何实现的,这样便于我们了解Vue.js的设计思想。

        在Vue.js1.x中,除了$emit()方法外,还提供了$dispatch()和$broadcast()这两个方法。$dispatch()用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在Vue实例的events选项内接收,实例代码如下:

<!--注意:该示例需使用Vue.js1.x的版本-->

<div id="app">

    {{ message }}

    <my-component></my-component>

</div>

<script>

    Vue.component(my-component',{

        template:'<buttonj @click="handleDispatch">派发事件</button>',

        methods:{

            handleDispatch:function(){

                this.$dispatch('on-message','来自内部组件的数据');

            }

        }

    }');

    var app = new Vue({

        el:'#app',

        data:{

        message:''

        },

        event:{

        "on-message":function(msg){

            this.message = msg ;

        }

    }

})

</script>

        同理,$broadcast()是由上级向下级广播事件的,用法完全一致,只是方向相反。

        这两种方法一旦发出事件后,任何组件都是可以接收到的,就近原则,而且会在第一次接收到后停止冒泡,除非返回true。

        这两个方法虽然看起来很好用,但是在Vue.js2.x中都废弃了,因为基于组件树结构的事件流方式让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱,并且不能解决兄弟组件通信的问题。

        在Vue.js2.x中,推荐使用一个空的Vue实例作为中央事件总线(bus),也就是一个中介。为了更形象的了解它,我们举一个生活中的例子:

        比如你需要租房子,你可能会找房产中介来登记你的需求,然后中介把你的信息发给满足要求的出租者,出租者再把报价和看房的时间告诉中介,由中介再转达给你,整个过程中,买家和卖家并没有任何交流,都是通过中介来传话的。

        或者你最近可能要换房了,你会找房产中介登记你的信息,订阅与你找房需求相关的资讯,一旦有符合你的房子出现时,中介会通知你,并传达你房子的具体信息。

        这两个例子中,你和出租者担任的就是两个跨级的组件,而房产中介就是这个中央事件总线(bus)。比如下面的示例代码:

<div id="app">

        {{ message }}

        <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-message','来自组件component-a的内容');

            }

        }

    });

    var app = new Vue({

        el:'#app',

        data:{

        message:''

        },

        mounted:function(){

        var _this = this;

    //在实例初始化时,监听来自bus实例的事件

    bus.$on('on-message',function(msg){

        _this.message = msg;

          });

        }

    })

</script>

        首先创建了一个名为bus的空Vue实例,里面没有任何内容;然后全局定义了组件component-a;最后创建Vue实例app,在app初始化时,也就是在生命周期mounted钩子函数里监听了来组bus的事件on-message,而在组件component-a中,点击按钮会通过bus把事件on-message发出去,此时app就会接受到来自bus的事件,进而在回调里完成自己的业务逻辑。

        这种方法巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级,而且Vue1.x和Vue2.x都适用。如果深入使用,可以扩展bus实例,给它添加data、methods、computed等选项,比如用户登录的昵称、性别、邮箱等,还有用户的授权token等。只需在初始化时让bus获取一次,任何时间、任何组件就可以从中直接使用了,在单页面富应用(SPA)中会很实用,我们会在进阶篇里逐步介绍这些内容。

        当你的项目比较大,有很多的小伙伴参与开发时,也可以选择更好的状态管理解决方案vuex,在进阶篇里会详细介绍关于它的用法。

        除了中央事件总线bus外,还有两种方法可以实现组件间通信:父链和子组件索引。

父链

        在子组件中,使用this.$parent可以直接访问该组件的父实例或组件,父组件也可以通过this.$children访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。示例代码如下:

<div id="app">

    {{ message }}

    <component-a></component-a>

</div>

<script>

    Vue.component('component-a',{

        template:'<button @click="handleEvent">通过父链直接修改数据</button>',

        methods:{

        handleEvent:function(){

                //访问到父链后,可以做任何操作,比如直接修改数据

                this.$parent.message = '来自组件component-a的内容';

            }

        }

    });

    var app =new Vue({

    el:'#app',

    data:{

        message:''

    }

})

</script>

        尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该主动的去修改它的数据,因为这样使得父子组件紧耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只有组件自己能修改它的状态。父子组件最好还是通过props和$emit来通信。

子组件索引

        当子组件较多时,通过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(){

            message:'子组件内容'

    },

    var app =new Vue({

        el:'#app',

        methods:{

            handleRef:fucntion(){

                var msg = this.$refs.comA.message;

                console.log(msg);

            }

        }

    })

});

</script>

        在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的子组件。

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

        与Vue1.x不同的是,Vue2.x将v-el和v-ref合并为了ref,Vue会自动去判断是普通标签还是组件。可以尝试补全下面的代码,分别打印出两个ref看看都是什么:

<div id="app">

    <p ref="p">内容</p>

    <child-component ref="child"></child-component>

</div>


7.4使用slot分发内容

7.4.1什么是slot

        看一个常规网站布局,一般由一级导航、二级导航、左侧列表、正文以及底部版权信息5个模块组成,如果要讲它们都组件化,这个结构可能会是:

<app>

    <menu-main></menu-main>

    <menu-sub></menu-sub>

    <div class="container">

        <menu-left<</menu-left>

        <container></container>

    </div>

    <app-footer></app-footer>

</app>   

        当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发(transclusion)。以<app>为例,它有两个特点:

<app>组件不知道它的挂载点会有什么内容。挂载点的内容是由<app>的父组件决定的。

<app>组件很可能有它自己的模板。

        props传递数据,enents触发事件和slot内容分发就构成了Vue组件的三个API来源,再复杂的组件也是由这三部分构成的。

7.4.2作用域

        正式介绍slot前,需要先指定一个概念:编译的作用域。比如父组件中有如下模板:

<child-component>

    {{ message }}

</chid-component>

      这里的message就是一个slot,但是它绑定的是父组件的数据,而不是组件<chid-component>的数据。

        父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。例如下面的代码 示例:

<div id="app">

<child-component v-show="showChild:></child-component>

</div>

<script>

Vue.component('child-component',{

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>

因此,slot分发的内容,作用域是在父组件上的。

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>

具名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">\

<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>元素,其中在<div class="main">内的<slot>没有使用name属性,它将作为默认slot出现,父组件没有使用slot特性的元素与内容都将出现在这里。

如果没有指定默认的匿名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>

在组合使用747组件时,内容分发API至关重要。

7.4.4作用域插槽

        作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染元素。概念比较难理解,我们先看一个简单的示例来了解它的基本用法。示例代码如下:

<div id="app">

    <child-component>

        <template scope="props">

            <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>

        观察子组件的模板,在<slot>元素上由一个类似props传递数据给组件的写法msg="xxx",将数据传到了插槽。父组件中使用了<template>元素,而且拥有一个scope="props"的特性,这里的props只是一个临时变量,就像v-for="item in items"里面的item一样。template内可以通过临时变量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>

Vue.component('my-list',{

    props:{

        books:{

            type:Array,

            default:function(){

                return []'

            }

        }

    },

    template:'\

        <ul>\

            <slot name="book" v-for="book in books" :book-name="book.name"></slot>\

        </ul>'

});

var app = new Vue({

    el:'#app',

    data:{

        books:[

            {name: '《Vue.js实战》'},

            {name: '《Vue.js实战之六个周》'},

            {name: '《Vue.js实战之简书六个周》'},

        ]

    }

})

</script>

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

        上示例注意是为了介绍作用域插槽的用法。

7.4.5访问slot

        在Vue.js1.x中,想要获取某个slot是比较麻烦的,需要用v-el间接获取。而Vue.js2.x提供了用来访问被slot分发的内容的方法$slots,请看下面的示例:

<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>',

    methods:{

        var header = this.$slot.header;

        var main = this.$slot.default;

        var footer = this.$slot.footer;

        console.log(footer);

        console.log(footer[0].elm.innerHTML);

    };

    var app = new Vue({

        el:'#app'

    })

});

</script>

        通过$slot可以访问某个具名slot,this.$slot.default包括了所有没有被包含在具名slot中的节点。尝试编写代码,查看两个console打印的内容。

        $slot在业务中几乎用不到,在用render函数(进阶篇中将介绍)创建组件时会比较有用,但注意还是用于独立组件开发中。





上一章:vue.js-表单与v-model(基础篇)

下一章:vue.js-自定义指令(基础篇)

推荐阅读更多精彩内容