Bootstrap

Vue2.5学习笔记(二)深入了解组件

1.组件使用的细节

1.2 子标签内使用组件

假设想在表格便签内使用一个组件作为行,直接使用组件是不行的,原因在于tbody内只能放tr便签,否则浏览器就会将tbody内的标签解析到外面,从而导致结构的错乱。
为了解决这个问题,我们可以使用is特性,将is特性设置为组件名称,这样既保证了tbody和tr之间的层级关系,也使用了组件。
在这里插入图片描述

1.2 非根组件的data必须是一个函数

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。

1.3 在Vue中操作DOM:使用ref属性

在Vue中,可以为DOM节点设置ref属性,从而可以通过实例名(实例引用this).$ref.属性名来访问DOM节点。
在这里插入图片描述

2.父子组件间传值

2.1 父组件向子组件传值:通过属性传值

Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。

<body>
    <div id="root">
        <counter :count="count1"></counter>
        <counter :count="count2"></counter>
    </div>
    <script>
        var counter = {
            props:['count'],
            data:function(){
                // 子组件不能直接修改父组件传过来的值,因此需要将附件传过来的值复制一份
                return {
                    number:this.count
                }
            },
            template:'<div @click="handleClick">{{number}}</div>',
            methods:{
                handleClick:function(){
                    this.number ++;
                }
            }
        }
        var vm = new Vue({
            el:"#root",
            data:{
                count1:0,
                count2:1
            },
            components:{
                counter:counter
            }
        })
    </script>
</body>

2.2 子组件向父组件传值:通过事件this.$emit()

<body>
    <div id="root">
        <counter :count="count1" @change="handleChange"></counter>
        <counter :count="count2" @change="handleChange"></counter>
        <div>{{total}}</div>
    </div>
    <script>
        var counter = {
            props:['count'],
            data:function(){
                // 子组件不能直接修改父组件传过来的值,因此需要将附件传过来的值复制一份
                return {
                    number:this.count
                }
            },
            template:'<div @click="handleClick">{{number}}</div>',
            methods:{
                handleClick:function(){
                    this.number ++;
                    this.$emit('change',1);
                }
            }
        }
        var vm = new Vue({
            el:"#root",
            data:{
                count1:0,
                count2:1,
                total:1
            },
            components:{
                counter:counter
            },
            methods:{
                handleChange:function(step){
                    this.total += step;
                }
            }
        })
    </script>
</body>
</html>

通过在子组件中触发自定义事件$emit('事件名',参数列表...),父组件可以监听到这个事件。

== 一句话总结 ==:父组件通过props给子组件传值,子组件通过事件触发给父组件传值。

2.3 组件参数校验

假设我们在子组件中限定,父组件必须传一个字符串给子组件。

<body>
    <div id="root">
        <child :content="content"></child>
    </div>
    <script>
    	// 全局子组件
        Vue.component('child',{
            props:{
                content:String
            },
            template:'<div>{{content}}</div>'
        })
        var vm = new Vue({
            el:'#root',
            data:{
                content:123
            }
        })
    </script>
</body>

当父组件传了一个数字后,Vue会警告如下:
在这里插入图片描述
当允许多个类型时,使用数组语法:

props:{
     content:[Number,String],//数组语法表示,该参数既可以接受Number类型也可以接受String类型
}

更详细的参数校验:

<body>
    <div id="root">
        <child :content="content"></child>
    </div>
    <script>
        Vue.component('child',{
            props:{
                content:{
                    type:String,
                    required:true,
                    default:'default value',
                    // 校验长度
                    validator:function(value){
                        return (value.length>5)
                    }
                }
            },
            template:'<div>{{content}}</div>'
        })
        var vm = new Vue({
            el:'#root',
            data:{
                content:"Hello,world"
            }
        })
    </script>
</body>

2.4 非props特性

若父组件向子组件传递了一个参数content,但子组件并未接受(即子组件的props属性内不包含content)时,父组件的content特性成为非props特性,当然在子组件中无法获取该特性的值。非props属性会显示在DOM属性中,而props属性不会。

2.5 给子组件绑定原生事件

当我们想给子组件上绑定原生事件,例如click,我们首先可能会想到这么做:

<body>
    <div id="root">
        <child @click="handleClick"></child>
    </div>
    <script>
        Vue.component('child',{
            template:'<div>Hello,world</div>'
        })
        var vm = new Vue({
            el:'#root',
            methods:{
                handleClick:function(){
                    alert("click");
                }
            }
        })
    </script>
</body>

这样做的结果是:无论点击多少次,都无法触发click事件处理函数。
原因在于,直接给子组件的DOM元素上绑定的事件全部为自定义事件,click也被识别为自定义事件,我们没有关于click的触发定义,因此不会触发handleClick。
那么怎么给子组件绑定原生事件呢?
我们需要把事件绑定到子组件的模板上,并且相关的事件处理函数也应该定义在子组件内。

<body>
    <div id="root">
        <child></child>
    </div>
    <script>
        Vue.component('child',{
            template:'<div @click="handleClick">Hello,world</div>',
            methods:{
                handleClick:function(){
                    alert("click");
                }
            }
        })
        var vm = new Vue({
            el:'#root',
        })
    </script>
</body>

要想触发原生事件,可以在子组件的原生事件处理函数中使用$emit('click')

<body>
    <div id="root">
        <child @click="handleClick"></child>
    </div>
    <script>
        Vue.component('child',{
            template:'<div @click="handleChildClick">Hello,world</div>',
            methods:{
                handleChildClick:function(){
                    alert("click child");
                    this.$emit('click');
                }
            }
        })
        var vm = new Vue({
            el:'#root',
            methods:{
                handleClick:function(){
                    alert("click");
                }
            }
        })
    </script>
</body>

以上的做法过于繁琐,Vuet提供了组件修饰符:

<child @click.native="handleClick"></child>

@事件名.native 表明该事件是一个原生事件

2.6 非父子组件之间的传值

在这里插入图片描述
方式一:使用Vuex
方式二:使用发布订阅模式(总线机制/BUS/发布订阅模/观察者模式)

下面介绍方式二:

Vue.prototype.bus = new Vue();

在Vue类的prototype上挂了一个bus属性,该属性是Vue类型的对象。因为是在原型上,Vue类的每个实例都会有一个bus属性,而且指向同一个Vue实例。
我们可以通过bus属性来统一地触发事件并携带参数。

<body>
    <div id="root">
        <child content="dell"></child>
        <child content="lee"></child>
    </div>
    <script>
        Vue.prototype.bus = new Vue();
        // 在Vue类的prototype上挂了一个bus属性,该属性是Vue类型的对象
        // 因为是在原型上,Vue类的每个实例都会有一个bus属性,而且指向同一个Vue实例
        Vue.component('child',{
            data:function(){
                return {
                    selfContent:this.content
                }
            },
            props:{
                content:String
            },
            template:'<div @click="handleClick">{{selfContent}}</div>',
            methods:{
                handleClick:function(){
                    this.bus.$emit('change',this.selfContent);
                }
            },
            mounted:function(){
                var that = this;
                this.bus.$on('change',function(msg){
                    that.selfContent = msg;
                })
            }
        })
        var vm = new Vue({
            el:"#root"
        })
    </script>
</body>

3.插槽

使用场景:当子组件中有一部分DOM元素时根据父组件传递过来的值进行渲染的,这时使用props传值会直接将HTML标签渲染在页面上,为了避免这一错误,我们可能需要使用div来包裹,并且使用v-html指令来确保其格式正确,如下:

<body>
    <div id="root">
        <child content='<h2>World</h2>'></child>
    </div>
    <script>
        Vue.component('child',{
            props:['content'],
            template:`<div>
                        <h1>Hello</h1>
                        <div v-html="content"></div>
                      </div>`
        })
        var vm = new Vue({
            el:"#root"
        })
    </script>
</body>

这样的方式在传递的代码量大的情况下会非常糟糕。
这种情况下,可以使用插槽

<body>
    <div id="root">
        <child>
            <h2>World</h2>
        </child>
    </div>
    <script>
        Vue.component('child',{
            props:['content'],
            template:`<div>
                        <h1>Hello</h1>
                        <slot></slot>
                      </div>`
        })
        var vm = new Vue({
            el:"#root"
        })
    </script>
</body>

在这个例子中,我们将需要插入到子组件的内容,直接以HTML标签的形式写在了子组件的占位标签内<child></child>,在子组件的模板中使用<slot></slot>标签可以接收到自己占位标签内的所有内容。
可以在插槽内指定默认内容,该内容会在父组件未给子组件的插槽传递值时显示,若传递了,则默认隐藏。

<slot>默认内容</slot>

具名插槽

为了在子组件中使用多个插槽,而每个插槽拥有不同的内容,我们需要给插槽起名:在外部,给传进插槽的DOM元素指定slot特性;在内部,给插槽指定name特性,与外部要传入该插槽的slot特性名一致。

<body>
    <div id="root">
        <body-content>
            <div class='header' slot='header'>header</div>
            <div class='footer' slot='footer'>footer</div>
        </body-content>
    </div>
    <script>
        Vue.component('body-content',{
            props:['content'],
            template:`<div>
                        <!-- header部分 -->
                        <slot name='header'></slot>
                        <div class='content'>content</div>
                        <!-- footer部分 -->
                        <slot name='footer'></slot>
                      </div>`
        })
        var vm = new Vue({
            el:"#root"
        })
    </script>
</body>

在这里插入图片描述

4.作用域插槽

子组件在渲染数据时,可以不指定具体的渲染样式,而由父组件来决定该数据渲染成什么样子。
做法:
在子组件的模板中定义插槽:

template:`<div>
                       <ul>
                        <slot v-for="item in list" :item=item></slot>
                       </ul>
                      </div>`

在父组件对应子组件的位置使用<template>标签声明作用域插槽,并使用slot-scope属性接受参数。
作用域插槽必须包裹在template标签内

            <template slot-scope="props">
                <li>{{props.item}}</li>
            </template>
<body>
    <div id="root">
        <child>
            <template slot-scope="props">
                <li>{{props.item}}</li>
            </template>
        </child>
    </div>
    <script>
        Vue.component('child',{
            data:function(){
                return{
                    list:[1,2,3,4]
                }
            },
            template:`<div>
                       <ul>
                        <slot v-for="item in list" :item=item></slot>
                       </ul>
                      </div>`
        })
        var vm = new Vue({
            el:"#root"
        })
    </script>
</body>

5.动态组件

如果我们想根据一些条件动态地决定渲染哪些组件,不渲染哪些组件,我们可以这样做:

<body>
    <div id="app">
        <child-one v-if="type === 'child-one'"></child-one>
        <child-two v-if="type === 'child-two'"></child-two>
        <button @click="handleSwtich">切换</button>
    </div>
    <script>
        Vue.component('child-one',{
            template:'<div>child-one</div>'
        })
        Vue.component('child-two',{
            template:'<div>child-two</div>'
        })
        var vm = new Vue({
            el:"#app",
            data:{
                type:'child-one'
            },
            methods:{
                handleSwtich:function(){
                    this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
                }
            }
        })
    </script>
</body>

以v-if的方式切换组件的渲染与否,每次切换都会有一个组件被销毁,有一个组件被渲染,若每次销毁和新建的组件内容一样,则会非常浪费性能。
为解决这个问题,可以使用v-once指令,使用了v-once指令的组件,会在第一次渲染时就被加载进内存,以后使用v-if切换时不会销毁和重新创建,而是直接从内存中加载,这样做能提高性能。

Vue提供了动态组件,可以更好地解决这个问题:

<component :is="type"></component>

<component>所代表的组件为动态组件,它的is特性指明它的类型。通过动态地改变is特性达到动态改变组件类型的目的。

        Vue.component('child-one',{
            template:'<div v-once>child-one</div>'
        })
        Vue.component('child-two',{
            template:'<div v-once>child-two</div>'
        })
;