2月6日-深入了解组件(6). 插槽

Profile Picture
- Published on Feb 6, 2020🌏 Public

深入了解组件

1. 组件的注册

1.1 组件的注册方式

组件有两种注册方式: 1. 全局方式 2. 局部注册

全局注册:

到目前为止我们使用的都是全局注册的方式,也就是说它们在注册之后,可以再任何新建的Vue实例中使用。

Vue.component('组件名',{
    //选项
})

局部注册:

new Vue({
    el:'#app',
    components:{
        '组件1':{
             // 选项
        },
        '组件2':{
            //选项
        }
    }
})

使用局部注册,拒不注册的组件在其他Vue实例中不可以使用,并且在其子组件中也不可以使用。

    <div id="app" >
       <aa></aa>
       <bb></bb>
    </div> 
   
    <script>
      var bb={
            template:"<span>bbbbbb</span>"
        }
        new Vue({
            el: "#app", 
            components: {
                'aa':{
                    //子组件当中,无法使用父组件注册的bb,那需要重新注册
                    template:"<span>1234214<bb></bb></span>",
                    components: {
                        'bb':bb
                    }
                },
                'bb':bb
            }
        })
      
    </script>

2. Prop

2.1. Prop的大小写

HTML当中对于属性名的大小写不敏感,所以浏览器会把所有的大写字符解释为小写字符,所以就意味着,我们在使用HTML作为模板代码时,prop名称为骆驼命名法的,要使用其等价的短横线分隔命名法。

iHaveAPen ==> i-have-a-pen

    <div id="app" >
      <aa i-have-a-pen="asdf"></aa>
    </div> 
    <script>
        new Vue({
            el: "#app", 
            components: {
                'aa':{
                    props:['iHaveAPen'],
                    template:'<span>1234{{iHaveAPen}}</span>'
                }
            }
        })
    </script>

如果我们是在template字符串中使用的时候,可以不适用短分割线命名法,因为template字符串是区分大小写的。

    <div id="app" >
    </div> 
   
    <script>
    
        new Vue({
            el: "#app", 
            components: {
                'aa':{
                    props:['iHaveAPen'],
                    template:'<span>1234{{iHaveAPen}}</span>'
                }
            },
            template:'<div> <aa iHaveAPen="asdf"></aa></div>'
        })
      
    </script>

2.2. Prop类型

目前为止,我们使用的prop定义都是:

    props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

这种方式,快速。我们现在要介绍的这种方式,写的会复杂些,但是,可以带来更好地类型检查和数据验证。

    props:{
        title:String,
        likes:Number,
        isPulished:Boolean,
        commentIds:Array,
        author:Object
    }

可用的数据类型:

String 字符串
Number 数字
Boolean 布尔值
Array 数组
Object 对象
Date 日期
Function 函数

2.2.传递静态或者动态数据

 <div id="app" >
        字符串动态: <aa :title="artice.title" ></aa>
        <hr>
        字符串静态: <aa title="yyy"></aa>
        <hr>
        数字动态:<aa :likes="artice.likes"></aa>
        <hr>
        布尔值静态,写了就是true:<aa is-pulished></aa> 
        <hr>
        布尔值动态:<aa :is-pulished='artice.isPulished'></aa>
        <hr>
        数组动态:<aa :comment-ids="artice.commentIds"></aa>
        <hr>
        对象动态:<aa :author="artice.author"></aa>
        <hr>
        一次性传入对象所有属性:<aa v-bind="artice"></aa>
    </div> 
   
    <script>
    
        new Vue({
            el: "#app", 
            data:{
                artice:{
                    title:"xxx",
                    likes:3,
                    isPulished:true,
                    commentIds:[1,2],
                    author:{
                        name:"小明"
                    }
                }
            },
            components: {
                'aa':{
                    props:{
                        title:String,
                        likes:Number,
                        isPulished:Boolean,
                        commentIds:Array,
                        author:Object
                    },
                    template:`<span>
                        title:{{title}},
                        likes:{{likes}},
                        isPulished:{{isPulished}},
                        commentIds:{{commentIds}},
                        author:{{author}}
                        </span>`
                }
            }
        })

2.3.单向数据传递。

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态

每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
我们只要看传入的数据,是在传入值类型还是引用类型就可以判断了。

我们在实际项目中,会遇到需要高边prop值的情况:

  1. 这个prop用来传递一个初始值,这个初始值随后会被子组件内容改变。这种情况下,最好的处理方式,就是建立一个组件内部的data属性,将这个prop值作为data属性的初始值。
 <div id="app" >
    <div>
        {{artice.title}}
        <input type="text" v-model="artice.title">
    </div>
    <hr>
    <aa :title="artice.title"></aa>
</div> 
<script>
    new Vue({
        el: "#app", 
        data:{
            artice:{
                title:"xxx"
            }
        },
        components: {
            'aa':{
                props:{
                    title:String
                },
                data(){
                    return {
                        //建立一个t属性,初值为title的值
                        t:this.title
                    }
                },
                template:`<div>
                    title:{{title}},
                    t:{{t}},
                    <input v-model="t">
                </div>`
            }
        }
    })
</script>
  1. 需要这个prop值进行一些转换,这种情况我们用computed创造经过计算之后的值
    <div id="app" >
        <div>
            {{artice.title}}
            <input type="text" v-model="artice.title">
        </div>
        <hr>
       <aa :title="artice.title"></aa>
    </div> 
    <script>
        new Vue({
            el: "#app", 
            data:{
                artice:{
                    title:"xxx"
                }
            },
            components: {
                'aa':{
                    props:{
                        title:String
                    },
                    template:`<div>
                        title:{{title}},
                        t:{{t}}
                    </div>`,
                    computed: {
                        t:function(){
                           return this.title.toUpperCase()
                        }
                    }
                }
            }
        })
      
    </script>

2.4.prop验证和类型检验

    props:{
        title:String,
        likes:[String,Number],
        isPulished:Boolean,
        commentIds:Array,
        author:Object
    }

可以扩展成下面的形式

    props:{
        title:{
            type:String,
            required:true,//必须传递
            default:'abc',
            //自定义验证规则,返回false时会在控制台报错
            validator:function(value){
               return   value.length>5;
            }
        },
        likes:{
            type:[String,Number]
        },
        isPulished:{
            type:Boolean,
            default:true //没有传递数据则默认值为true
        },
        commentIds:{
            type:Array,
            //对于Array和Object类型,需要提供数据构造函数来返回数据
            default:function(){
                return [1,2,4]
            }
        },
        author:{
            type:Object
        }
    }

2.5. 非Prop的属性

Vue会把父组件加在子组件上的属性,自动附加到子组件的根元素上。

 <div id="app">
        <div>
            {{artice.title}}
            <input type="text" v-model="artice.title">
        </div>
        <hr>
        <aa class="abc" style="color:red"  a="1" b="2" :likes="'123'"></aa>
    </div>
    <script>
        new Vue({
            el: "#app",
            data: {
                artice: {
                    title: "xxx"
                }
            },
            components: {
                'aa': {
                    props: {
                        title:{
                            type:String,
                            default:'abc1234',
                             //自定义验证规则
                            validator:function(value){
                                return   value.length>5;
                            },
                           
                        },
                        likes: {
                            type: [String, Number]
                        },
                        isPulished:{
                            type:Boolean,
                            default:true
                        }, 
                        commentIds:{
                            type:Array,
                            default:function(){
                                return [1,2,4]
                            }
                        },
                       
                    },
                    template: `<div>
                      {{title}} {{likes}}  {{isPulished}} {{commentIds}}
                    </div>`,
                    computed: {
                        t: function () {
                            return this.title.toUpperCase()
                        }
                    }
                }
            }
        })

    </script>

3. 自定义事件

3.1. 自定义事件命名

事件名不像prop名称、组件名称,事件名不存在任何大小写的自动转换,而是触发的事件名需要和监听的名称完全匹配。

举个例子,比如我们要触发talkSomething事件

this.$emit('talkSomething')

如果我们按之前的思维,将驼峰命名法改为短横线命名法去监听。

<!--无效-->
<aa v-on:talk-something="doSomething"></aa>
<aa v-on:talkSomething="doSomething"></aa>
talk-something   => talk-something
talkSomething    => talksomething

所以我们发现,无论是talk-something还是talkSomething,由于html属性中不区分大小写,会全部理解成小写字符,所以无法跟有大写字符的talkSomething事件相匹配。

vue当中的自定义事件,名称中不要出现大写字符,如果需要做单词分隔的,使用短横线命名法。

 <div id="app">
        {{a}}
        <aa @talk-something="doSomething"></aa>
    </div>
    <script>
        new Vue({
            el: "#app",
            components: {
                'aa': {
                    template:`<button @click="$emit('talk-something')">xxxxx</button>`
                }
            },
            methods: {
                doSomething(){
                    this.a="asdfjsoidfj";
                }
            },
            data:{
                a:''
            }
        })

    </script>

3.2. 将原生事件绑定到组件根节点

通过.native,可以实现对组件根节点监听浏览器原生事件。

注意,使用这种方式只能监听浏览器原生事件,

    <div id="app">
        {{a}}
        <aa @input.native="doSomething" ></aa>
    </div>
    <script>
        new Vue({ 
            el: "#app",
            components: {
                'aa': {
                    template:`<div>  {{x}} <input > </div>`
                }
            },
            methods: {
                doSomething(){
                    this.a="监视到input事件";
                }
            },
            data:{
                a:''
            }
        })

    </script>

4.动态组件

有的时候,我们需要在不同的组件之间进行切换。

<component is="bb"></component>

通过vue自带的component组件,我们可以动态加载其他组件。

默认情况下,组件失活之后,会被销毁,如果我们让失活的组件被缓存下来,那只需要在外侧包一层

<keep-alive>
    <component :is="cname"></component>
</keep-alive>
    <div id="app">
        <input type="text" v-model="cname">
        <keep-alive>
            <component :is="cname"></component>
        </keep-alive>
        
    </div>
    <script>
        new Vue({ 
            el: "#app",
            components: {
                'aa': {
                    template:`<div>A <input  > </div>`
                },
                'bb':{
                    template:`<div>B <input > </div>`
                }
            },
            data:{
                cname:'aa'
            }
        })

    </script>

练习10

  1. 点击左侧导航菜单,如果没有改标签页,就新增一个。并且切换到显示该标签页内容。
  2. 点击标签上的xx,可以将标签页关闭

5. 插槽

5.1. 后备内容

后备内容很好理解,就是插槽内可以放置默认内容,如果使用插槽的时候有传入插槽的内容的话就用传入的,没有的话就会使用默认内容。

    <div id="app">
        <aa>
             111
        </aa>
    </div>
    <script>
          new Vue({ 
            el: "#app",
            components: {
                'aa': {
                    template:`<div> 
                        <p>1</p>
                        <slot name="default">
                            <button>asdfasdf</button>
                            <button>asdfasdf</button>
                        </slot>
                        <p>2</p>
                    </div>`
                },
               
            }
        })

    </script>

5.2. 作用域插槽

有时候我们需要在使用插槽的时候,从父组件访问子组件中的数据。这时候就会用到作用域插槽。

作用域插槽就是在定义插槽的时候,在slot标签上通过v-bind指令去绑定prop,可以绑定多个。

然后父组件去使用这个插槽的时候,通过v-slot指令等号后面的部分,去定义一个变量,接收子组件插槽提供的数据。

    <div id="app">

        <aa>
            <template v-slot:thead>
                <tr><th>产品名</th><th>单价</th><th>件数</th><th>小计</th></tr>
            </template>
            <template v-slot:default="row">
                <tr>
                    <td>{{row.x.name}}</td>
                    <td>{{row.x.price}}</td>
                    <td>{{row.x.count}}</td>
                    <td>{{row.x.count*row.x.price}}</td>
                </tr>
            </template>
        </aa>
    </div>
    <script>
          new Vue({ 
            el: "#app",
            components: {
                'aa': {
                    template:`<div> 
                        <table>
                            <slot name="thead" ></slot>
                            <slot :x="item" :y="1234"  v-for="item in items" ></slot>
                        </table>
                    </div>`,
                    data(){
                        return {
                            items: [{name:'乒乓球',price:20,count:10},{name:'足球',price:100,count:1}]
                        }
                    }
                }
            }
        })
    </script>