深入理解vue组件

1,062 阅读7分钟

一、组件中的细节点

is的使用

html语法中,table标签内就必须是tr标签,tr标签下就必须是td标签,ul标签下就必须是li标签 等等 标签间的嵌套规则

在运用组件的时候,会出现想在这个标签内使用组件,但这个标签内并不支持的情况,如下

<div id="app">
      <table>
        <tbody>
        	<tr>
            	<td>this  is a row</td>
            </tr>
            <tr>
            	<td>this  is a row</td>
            </tr>
            <tr>
            	<td>this  is a row</td>
            </tr>
        </tbody>
      </table>
    </div>

我们看到tr标签这一部分重复了几次,我们可以试着用组件的方式,写出它,如下

<div id="app">
      <table>
        <tbody>
          <row></row>
          <row></row>
          <row></row>
        </tbody>
      </table>
    </div>

    <script>
      Vue.component("row", {
          template:"<tr><td>this is a row</td></tr>"
      });
      var app = new Vue({
        el: "#app"
      });
    </script>

然后,我们打开浏览器测试下,

1557638536778

虽然展示结果一样,但我们可以看待tr这部分标签出现在了table标签外部,这就是table标签不支持内部嵌套不是tr标签的情况

这时候,我们就可以使用is了

<table>
    <tbody>
    	<tr is="row"></tr>//采用table内支持的标签,is后面接组件的名字
    </tbody>
</table>

这里就不贴图了,这时,table标签的嵌套就正常了

组件中data的使用

在子组件中,data必须是一个函数,并且该函数要返回一个对象

这是因为子组件会重复使用,而他们之间需要有独立数据存储内存,这样数据才不会互相影响,如果在组件中data是一个对象,那么,所有组件的数据都指向同一块内存。

需要操作dom实例时,$refs出现了

如果你需要在父组件中,直接访问子组件的实例

可以通过ref给子组件加个id

ref="one"

然后父组件中,这样访问

this.$refs.one

现在我们用ref来做个小例子

录制_2019_05_12_14_03_45_881

当我们点击上面的两个小数,会自增,最下的数字是两个小数是总回

子组件代码

Vue.component("count",{
        data:function(){
            return {
                number:0,
            }
        },
        template:"<div @click='add'>{{number}}</div>",
        methods:{
            add:function(){
                this.number++;
                this.$emit("change")//当被点击时,会触发父组件的change事件
            }
        }
    })

html代码

<div id="app">
        <count ref="one" @change="allNum"></count>//用ref给子组件设置一个id,one
        <count ref="two" @change="allNum"></count>
        <div>{{all}}</div>
    </div>

父组件代码

var app = new Vue({
        el:"#app",
        data:{
            all:0
        },
        methods:{
            allNum:function(){
                this.all=this.$refs.one.number+this.$refs.two.number;
                //在父组件中,可以通过this.$refs.ref来获取子组件的实例,
            }
        }
    })

二、父子组件间的传值

父组件想要给子组件传值,必须在调用子组件的html上写上要传值的属性名和值,如下

<count :count="3"></count>这里要注意:属性名后面接的都是一个js表达式,所以这里的3是一个数值类型

而在子组件中,要接受父组件的传值,则必须在propps:[]中加一个同样的属性名,props:["count"]

重点,在这里要注意vue的单向数据流概念,就是父组件可以给子组件传值,但子组件不能随意直接修改父组件传过来的值,必须先将父组件传过来的值复制一份,放在子组件的data中后,然后对data中的值进行修改

如下

Vue.component("count",{
    props:['count'],
    data:function(){
        return {
            number:this.count
        }
    }
})

如上,只能对父组件传过来的值的副本进行操作,这是因为,传进的是基础数据类型还好,如果父组件传递的是一个引用型的数据,那么子组件直接改动这个数据,就有可能对其他引用该数据的组件造成干扰

子组件给父组件传值,要借助$emit去触发父组件自定义的事件,

this.$emit("change",1);change是父组件自定义的事件,而1,就是我们要传递的值了,

现在我们来利用父子组件的传值,做一个简单的计数器

<div id="app">
        <count :count="one" @change="allNum"></count>
    //@change是父组件自定义的事件,触发了change//就会去执行allNum函数
        <count :count="two" @change="allNum"></count>
    //:count 是要传递的属性名,two是要传递的值,在data中,two对应0;
        {{all}}
    </div>
    <script>
    var count = {
        props:['count'],//用count来接受父组件传递过来的值
        data:function(){
            return {
                number:this.count,//记得单项数据流,要将父组件的传值,复制一份,放在子组件data中
            }
        },
        template:'<div @click="add">{{number}}</div>',
        methods:{
            add:function(){
                this.number++;
                this.$emit("change",1);//触发change事件,传值是1;
            }
        }
    }
    var app = new Vue({
        el:"#app",
        data:{
            all:0,
            one:0,
            two:0
        },
        components:{
            count:count,
        },
        methods:{
            allNum:function(value){//value来接受$emit的传值
                this.all+=value;
            }
        }
    })

效果如下:

录制_2019_05_12_21_11_30_550

三、组件参数校验与非Props特性

组件参数校验

当父组件给子组件传值时,我们希望对该值进行约束,如下

props:{
    content:{
        type:String,//限制传过来的值必须是字符串,
        required:false,//是否必须传递该值
        default:'default value',//默认值
        validator:function(value){//
            return (value.length>5);//该值的长度必须大于5
        }
    }
}

props特性

父组件给子组件传值时,切好子组件的props中有对应的prop进行接收,那么这就是props特性,,父组件给子组件传递的值,不会显示在html中,

向相反,非props特性,则会显示在html中

四、给组件绑定原生事件

父组件在子组件上绑定了一个事件,想要触发该事件,只能通过触发自定义事件的方式去触发,或者在加上事件修饰符.native

先看第一种,不加修饰符,不触发自定义事件

<div id="app">
        <child @click="onClick"></child>
    </div>
    <script>
    Vue.component('child',{
        template:'<div>child</div>',
    })
    var app = new Vue({
        el:"#app",
        methods:{
            onClick:function(){
                alert('click');
            }
        }
    })
    </script>

在浏览器中,我们发现,点击child并没有任何效果,

录制_2019_05_13_16_15_36_19

这就是想要触发父组件在子组件上绑定的事件,只能通过触发自定义事件的方式去触发,现在改代码如下:

<div id="app">
        <child @click="onClick"></child>
    </div>
    <script>
    Vue.component('child',{
        template:'<div @click="childClick">child</div>',
        methods:{
            childClick:function(){
                this.$emit("click");//触发父组件的自定义事件,click
            }
        }
    })
    var app = new Vue({
        el:"#app",
        methods:{
            onClick:function(){
                alert('click');
            }
        }
    })
    </script>

录制_2019_05_13_16_18_18_610

或者给加上一个事件修饰符.native也可以达到相同的效果

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

五、非父子组件间的传值

这里先介绍一种总线的模式。

<div id="app">
        <child content="胡"></child>
        <child content="志武"></child>
    </div>
    <script>
        Vue.prototype.bus = new Vue();
        Vue.component("child",{
            props:['content'],
            data:function(){
                return {
                    name:this.content,
                }
            },
            template:'<div @click="exchange">{{name}}</div>',
            methods:{
                exchange:function(){
                    this.bus.$emit("change",this.name)
                }
            },
            //组件被挂载时,
            mounted:function(){
                var that = this;
                this.bus.$on("change",function(msg){
                    that.name=msg;
                })
            }
        })
        var app = new Vue({
            el:"#app",
        })
    </script>

我们在Vueprototype上挂载了一个bus,这个bus是一个vue的实例,子组件要互相传值时,就可以用这个共同的祖先bus来监听和触发相应的事件,并借助这个共同的祖先来传值

1557737928725

六、使用插槽

slot可以让我们在组件的某个位置插入想要的内容

 Vue.component("child",{
            template:`<div>
                        <slot name="header">默认内容</slot>
                        <div>content</div>
                        <slot name="footer">默认内容</slot>
                      </div>`
        })V
<div id="app">
        <child>
            <div slot="header">我是头部</div>
            <div slot="footer">我是尾部</div>
        </child>
    </div>

如果在插槽位置没有插入内容,则会显示默认内容

作用域插槽

当子组件在做遍历,而希望这个遍历的dom结构由外部决定时,可以使用作用域插槽

<div id="app">
        <child >
            <template slot-scope="props">//template是必须写的
                <h1>{{props.item}}</h1>//props可以任意写,而item必须和template中的`:item`一致
            </template>
        </child>
    </div>
    <script>
        Vue.component("child",{
            data:function(){
                return {
                    list:[1,2,3,4,5]
                }
            },
            template:`
                    <ul>
                        <slot v-for="item of list"
                            :item=item>//这里的:item是要传值给html实际插槽部分的
                        </slot>
                    </ul>
            `
        })
        new Vue({
            el:"#app",
        })
    </script>

动态组件和v-once

<component>是vue自带标签,

<component :is="组件名">component会根据is后面组件名的不同而动态加载不同的组件

v-once可以把组件放在内存中,可以有效提高性能

template:`<div v-once>child ONE</div>`

结语

因为本人水平有限,如果有错漏的地方,还请看官多多指正

本文作者胡志武,写于2019/5/13,如果要转载,请注明出处,

如果觉得写的不错, 请点个赞吧