Vue 06 组件

Vue 06 组件

组件基础概念 全局注册 局部注册 is属性强制挂载组件 组件的其他属性 父组件向子组件传递数据:props使用字符串数组 单向数据流 父组件向子组件传递数据:props使用对象 组件基础概念 一个组件在HTML里就像是一个自定义标签,一个组件同时也是一个Vue实例,会根据实例的内容被渲染成实际的样子

  1. 组件基础概念
  2. 全局注册
  3. 局部注册
  4. is属性强制挂载组件
  5. 组件的其他属性
  6. 父组件向子组件传递数据:props使用字符串数组
  7. 单向数据流
  8. 父组件向子组件传递数据:props使用对象

组件基础概念

一个组件在HTML里就像是一个自定义标签,一个组件同时也是一个Vue实例,会根据实例的内容被渲染成实际的样子。 一个组件可以单独使用,也可以放在其他实例的内部使用,但在使用之前,都需要注册。 注册有两种方式,全局注册和局部注册。全局注册就是直接写在js代码或者文件的外层,这样在其后的所有代码都可以使用这个组件。局部注册就是在一个Vue实例的内部进行注册。 全局和局部并不是对于页面来说的。仅仅进行注册,而不在Vue实例的$el属性绑定的元素内,是无法使用组件的(因为HTML会把其当做一个普通的元素,在Vue内部才能对组件进行渲染。

全局注册

可以认为组件也是一个Vue实例,只不过写法和普通的Vue实例有些区别,现在先看一个最简单的全局注册例子,即组件的template属性可以渲染HTML元素:
<div id="app">
<my-comp></my-comp>
</div>
<script>
    // 全局注册一个叫做my-comp的组件
    Vue.component('my-comp', {
        template: "<div>组件内容</div>",

    });

    //这是使用组件的实例,也是my-comp组件的父实例
    var app = new Vue({
        el:"#app",

    })
</script>
这个例子会实际显示出template属性中的字符串以HTML解析后的内容,也就是组件内容四个字。 template属性中的内容,必须包含在HTML标签内才会被渲染,如果写成<div>组件内容</div>123,运行页面后可以看到Vue的提示:
[Vue warn]: Error compiling template:

text "321" outside root element will be ignored.

1  |  <div>组件内容</div>321
   |                 ^^^

found in

---> <MyComp>
       <Root>
即321不会被渲染。

局部注册

局部注册是使用Vue实例的components属性进行注册。把上边全局注册的例子改成如下:
<div id="app">
    <my-comp></my-comp>
</div>


<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div>组件内容</div>"
            }
        }

    })
</script>
只要是一个带有template属性的对象,就可以注册为一个组件,后边还会学到许多其他的属性。

is属性强制挂载组件

有些HTML标签,其中默认不会出现其他的元素,比如table内部。
<div id="app">
    <table>
        <tbody>
        <tr>
            <td>1</td>
        </tr>
        <my-comp></my-comp>
        <tr>
            <td>2</td>
        </tr>
        </tbody>
    </table>
</div>
如果是普通渲染,会发现组件被自动移动到了表格外部,如果想让组件强制显示,可以使用is属性:
<div id="app">
    <table>
        <tbody>
        <tr>
            <td>1</td>
        </tr>
        <tr>
            <td is="my-comp"></td>
        </tr>
        <tr>
            <td>2</td>
        </tr>
        </tbody>
    </table>
</div>
此时这个td标签,会被替换成组件的div标签。这就是强制渲染。像ol,ul,select元素都有这方面的限制。

组件的其他属性

除了template之外,组件中也可以使用data、computed、methods等属性,除了data之外都和原来的一样。 data属性则必须是一个匿名函数,返回一个对象,对象中包含属性名称和值即可。
<div id="app">
    <my-comp></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>{{message}}</p><input type='text' v-model='message'><p><button @click='alertMessage'>alert</button></p></div>",
                data:function () {
                    return {
                        message:"组件的message属性",
                        count: 0
                    }
                },
                methods:{
                    alertMessage:function() {
                        this.count++;
                        console.log(this.message + this.count);
                    }
                }
            }
        }

    })
</script>
data返回的对象中,如果引用到了外部对象,则会同时修改对象的属性的值。这点和普通的Vue实例是一样的。 这里不要受到返回一个对象的干扰,在组件中,依然可以使用this.属性名来找到对应的data属性。只是形式的变化,用法没有发生变化。

父组件向子组件传递数据

为什么要有组件,主要是为了可以方便的复用。想象一下这个场景:在一个聊天页面中,很多地方都需要显示用户的名称。 如果不使用组件,而是使用Vue实例控制每个小区域,那么这些组件都要互相去找用户使用的名称。 如果使用组件,让组件进行父子组件之间的通信,只要给父组件传递数据,数据就自动会被放到子组件中更新和处理,就会非常方便,也利于解耦。 props属性用于在组件中声明需要从父组件中接收的数据,值可以有两种,一种是字符串数组代表的各个变量名称,一种是对象。 先来看一个字符串数组的例子
<div id="app">
    <my-comp message="From Father Vue instance"></my-comp>
</div>

<script>
    var app = new Vue({
            el: "#app",
            components: {
                "my-comp": {
                    template: "<div><p>{{message}}</p></div>",
                    props: ['message'],
                    data:function () {
                        return {
                            count: 0
                        }
                    }
                }
            }
        })
</script>
props属性中的字符串数据中的每一个元素实际上是属性名称,就和data返回的count属性一样可以在子组件的作用域内使用。二者的不同是data返回的属性只能在子组件作用域内使用,而props的值来自于父级。要传递多个 如果把父组件的传递的内容绑定上属性,在父组件内就可以控制子组件的数据了:
<div id="app">
    <my-comp :message="mess"></my-comp>
    <input type="text" v-model="mess">
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>{{message}}</p><p>{{message2}}</p></div>",
                props: ['message'],
                data:function () {
                    return {
                        count: 0
                    }
                }
            }
        },
        data:{
            mess: ""
        }
    })
</script>
这个例子是把父组件传递给子组件的数据绑定给父组件的input框,输入什么,子组件就显示什么。 唯一要注意的是如果在HTML属性名中使用了减号,props中的属性名称要写成驼峰:
<div id="app">
    <my-comp :message-new="mess"></my-comp>
    <input type="text" v-model="mess">
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>{{messageNew}}</p></div>",
                props: ['messageNew'],
                data:function () {
                    return {
                        count: 0
                    }
                }
            }
        },
        data:{
            mess: ""
        }
    })
</script>
如果props里写成了message-new是没有作用的。 使用v-bind的好处是,可以传递除了字符串以外的值。如果直接写死,传递的都是字符串。使用v-bind可以传递对象或者其他类型的数据:
<div id="app">
    <my-comp :message1="mess" message2="[1,2,3,4]"></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>message1的长度是:{{message1.length}}</p><p>message2的长度是:{{message2.length}}</p></div>",
                props: ['message1','message2'],
                data:function () {
                    return {
                        count: 0
                    }
                }
            }
        },
        data:{
            mess: [1,2,3,4]
        }
    })
</script>
可以看到message1的长度是4,即数组的元素数,而message2的长度是9,是字符串的长度。 这里只需要在<my-comp :message1="mess" message2="[1,2,3,4]"></my-comp>中,将message2前边加上一个:或者v-bind:指令,传给子组件的就是js表达式解析出的数组而不是字符串了。

单向数据流

为什么要提到单向数据流,因为在Vue 1.x中,子组件修改props属性会连带修改父组件的数据。在Vue 2.x中就不行了,这是避免子组件污染父组件。看这个例子:
<div id="app">
    <my-comp :message="mess"></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>message是:{{message}}</p><button @click='modify'>修改子组件数据</button></div>",
                props: ['message'],
                data:function () {
                    return {
                        count: 0
                    }
                },
                methods:{
                    modify:function () {
                        this.message++;
                    }
                }
            }
        },
        data:{
            mess: 100
        }
    })
</script>
父组件传给子组件一个数据是100,子组件用按钮来直接将其自增。每点一次按钮,控制台内会有Vue的警告:
vue.js:634 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"
found in
---> <MyComp>
       <Root>
这里会警告不要直接修改父组件的对应数据,实际上也没能修改成功,在控制台里查看app.mess依然是100。 但是这样很不好,父子组件的数据其实没同步。Vue也给出了解决方案,就是在data里创建一个自己的局部变量用来接受初始值,或者使用计算属性基于传进来的值计算出新的值。 例子可以修改成这样:
<div id="app">
    <my-comp :message="mess"></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>initCount是:{{initCount}}</p><p>计算后的是{{computedCount}}</p><button @click='modify'>修改子组件数据</button></div>",
                props: ['message'],
                data:function () {
                    return {
                        initCount: this.message
                    }
                },
                methods:{
                    modify:function () {
                        this.initCount++;
                    }
                },
                computed:{
                    computedCount: function () {
                        return this.initCount + 100000;
                    }
                }
            }
        },
        data:{
            mess: 100
        }
    })
</script>
不过要注意的是,如果传递了一个对象比如数组,由于JS传递的是引用,所以依然会被修改:
<div id="app">
    <my-comp :message="mess"></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div><p>initCount是:{{initCount}}</p><p>计算后的是{{computedCount}}</p><button @click='modify'>修改子组件数据</button></div>",
                props: ['message'],
                data:function () {
                    return {
                        initCount: this.message
                    }
                },
                methods:{
                    modify:function () {
                        this.initCount.push(666);
                    }
                },
                computed:{
                    computedCount: function () {
                        return this.initCount.length;
                    }
                }
            }
        },
        data:{
            mess: [1, 2, 3, 4, 5]
        }
    })
</script>
每次点击按钮,查看父组件的mess属性,会发现也一并发生了变化。 因此子组件最好不要直接修改父组件的数据,尤其父组件的数据还有其他用途的时候。

props传递对象

当传入的数据需要验证的时候,就可以使用对象传递,并在每个属性名后跟上需要验证的类型,有特定的写法,看几个例子:
<div id="app">
    <p>传给子组件的是{{message}}</p>
    <my-comp :message6="message"></my-comp>
</div>

<script>
    var app = new Vue({
        el: "#app",
        components: {
            "my-comp": {
                template: "<div>{{message6}}</div>",
                props: {
                    // 传入Number类型的值
                    message1:Number,

                    //String和Number类型
                    message2:[String,Number],

                    //布尔类型,默认是true
                    message3:{
                        type:Boolean,
                        default: true
                    },

                    //Number类型的必传值
                    message4:{
                        type: Number,
                        required: true
                    },

                    //数组或者对象类型,必须用一个函数来返回
                    message5:{
                        type:Array,
                        default: function () {
                            return []
                        }
                    },

                    //自定义验证器
                    message6:{
                        validator:function (value) {
                            return value.length >= 5 && value instanceof Array;
                        }
                    }
                },
            }
        },
        data: {
            message: [1, 3, 4, 5,5]
        }
    })
</script>
如果类型不匹配,在控制台里可以看到警告。 type支持的类型有:String,Number,Boolean,Object,Array,Function。type也可以传入一个自定义验证器,用instanceof来验证:
message5:{
    type:function (obj) {
        return obj instanceof Function;
    },
    default: function () {
        return []
    }
},
LICENSED UNDER CC BY-NC-SA 4.0
Comment