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

文章目录

  • 1.组件使用的细节
    • 1.2 子标签内使用组件
    • 1.2 非根组件的data必须是一个函数
    • 1.3 在Vue中操作DOM:使用ref属性
  • 2.父子组件间传值
    • 2.1 父组件向子组件传值:通过属性传值
    • 2.2 子组件向父组件传值:通过事件this.$emit()
    • 2.3 组件参数校验
    • 2.4 非props特性
    • 2.5 给子组件绑定原生事件
    • 2.6 非父子组件之间的传值
  • 3.插槽
  • 4.作用域插槽
  • 5.动态组件

1.组件使用的细节

1.2 子标签内使用组件

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

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

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

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

在Vue中,可以为DOM节点设置ref属性,从而可以通过实例名(实例引用this).$ref.属性名来访问DOM节点。
Vue2.5学习笔记(二)深入了解组件_第2张图片

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:'
{{number}}
'
, 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:'
{{number}}
'
, 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:'
{{content}}
'
}) 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:'
{{content}}
'
}) 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:'
Hello,world
'
}) 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:'
Hello,world
'
, 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:'
Hello,world
'
, 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 非父子组件之间的传值

Vue2.5学习笔记(二)深入了解组件_第3张图片
方式一:使用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:'
{{selfContent}}
'
, 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='

World

'
></child> </div> <script> Vue.component('child',{ props:['content'], template:`

Hello

`
}) 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:`

Hello

`
}) var vm = new Vue({ el:"#root" }) </script> </body>

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

<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:`
content
`
}) var vm = new Vue({ el:"#root" }) </script> </body>

Vue2.5学习笔记(二)深入了解组件_第4张图片

4.作用域插槽

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

template:`
`

在父组件对应子组件的位置使用