Vue.js组件入门指南(1.0版)

发表时间: 2020-03-25 20:05

组件介绍

组件是Vue.js中一个非常重要的概念,它是一种抽象,提供独立,可复用的小组件来构建大型应用。你可认为所见任意类型的网页都可抽象为一个组件树,比如像下面这样的:

引入组件的目的是扩展HTML元素,并不是为了代替HTML元素(事实上组件更像是自定义的HTML标签,类似于XML),同时组件可封装一些使用频率较高的HTML代码,实现代码的高复用。

组件使用

组件的使用分为3个步骤:第一,创建组件构造器;第二,注册组件;第三,使用组件。第一,创建组件构造器,使用Vue.extend()方法(请注意不是extends方法),里面传入一个选项对象。第二,注册组件,使用Vue.components()方法,里面传入2个参数,第1个是参数组件标签,第2个参数是一个组件构造器(当然也可以是选项对象,使用选项对象的template属性定义组件模板。使用这种方式Vue会自动地调用Vue.extend()方法)。第三,使用组件,必须在Vue实例的作用范围内使用组件,否则不会生效。

<body><div id="app"><!--3、在Vue实例的作用范围内使用组件(#app是Vue实例挂载的元素,x需要在该元素内使用组件) -->    <my-component></my-component></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    //1、创建一个组件构造器    var myComponent = Vue.extend({        template:"<h1 style='color: red'>hello,Vue,js!</h1>"    });    //2、注册组件并指定组件的标签,组件的HTML标签为<my-component>    Vue.component("my-component",myComponent);    //实例化一个Vue对象    new Vue({        el:"#app",    });</script></body>

运行结果:

仔细观察发现组件确实就是自定义的XML标签。注意:调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。

new Vue({        el:"#test",  });<div id="test">      <my-component></my-component></div>

运行结果:

结果和上面一样!但是你单纯的只是使用这个组件,而不放在挂载元素下方是不会生效的,就像下面的例子。

<!--该组件不会被渲染--><my-component></my-component>

局部注册

前面说了调用Vue.component()注册组件时,组件是被全局注册,那如果仅仅只是想让某个组件只在某个实例内使用,那该如何注册呢?这时局部注册就产生了。你可以在选项对象的components属性中实现局部注册,其实就是直接在Vue的实例中注册,举个例子:

<!--3、在在Vue实例的作用范围内使用组件--><div id="hello">    <local-component></local-component></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">   //局部注册    //1、创建一个局部的组件构造器    var localComponent = Vue.extend({  //注意是extend方法,而不是extends方法        template: "<h1 style='color: blue'>Local Component!</h1>"    });    new Vue({        el:"#hello",        components:{            //2、将localComponent组件注册到#hello这个Vue实例下面            "local-component":localComponent,        }    });</script>

运行结果:


此时你再将这个组件挂载到id非hello的元素上时,运行发现没有内容输出:

<div id="test"><!-- 无法使用local-component组件,因为local-component是局部组件,仅仅属于#hello -->    <local-component></local-component></div><script>    new Vue({        el: '#test'    });</script>

父组件与子组件

组件之间的嵌套使用形成了父组件与子组件这种关系。

<div id="app">    <parent-component></parent-component></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    var child = Vue.extend({        template:"<p style='color: green'> This is the child component!</p>"    })    var parent = Vue.extend({        template:"<p>This is the parent component!<child-component></child-component></p>",         components:{            "child-component":child        }    });    Vue.component("parent-component",parent);    new Vue({        el:"#app",    })</script>

运行结果:

首先创建了一个child组件构造器,然后在parent组件中直接引入child的组件标签,同时将child组件以局部注册方式注册到parent中,最后将parent组件全局注册到Vue实例中。下面是该过程的流程示意图:

需要说明的是child组件在Parent组件以局部方式注册,因此仅在parent组件中使用,child组件标签只能在parent 组件的template中使用。这句话怎么理解?看下面的例子你就知道了:

去掉parent组件中引入的child组件标签<child-component></child-component></p>

var parent = Vue.extend({        template:"<p>This is the parent component!",         components:{            "child-component":child        }    });

然后再来运行一下代码:


这似乎是意料之中的事,因为尚未使用child组件标签,此时按照如下方式引入:

<div id="app">    <parent-component>        <child-component></child-component>    </parent-component></div>

运行结果:



为什么还是无法显示呢,明明已经引入了子组件标签?其实原因是:当子组件注册到父组件时,Vue.js会编译好父组件的模板,模板的内容决定父组件将要渲染的HTML。<parent-component></parent-component>父组件之间的内容在运行时,子标签只会当作普通的HTML来执行,而<child-component></child-component>不是标准的HTML标签,因而会被浏览器直接忽视掉,所以无法显示。


理解了这个,再来看这种方式的引入:

<div id="app">    <parent-component></parent-component>    <child-component></child-component></div>

自然这种方式也是会报错的,相反只要你在父组件的template中使用了子组件标签,那么这两种方式其实都是可以运行的,即下面这三种方式可以达到相同的效果:

<div id="app">    <parent-component></parent-component></div><div id="app">    <parent-component>        <child-component></child-component>    </parent-component></div><div id="app">    <parent-component></parent-component>    <child-component></child-component></div>

组件注册语法糖

在前面组件使用的第一步是创建组件构造器,每次都需要使用Vue.extend()方法,这个步骤有些多余,Vue.js提供了注册语法糖来解决这个问题。在前面也多次提到过,注册组件可以使用Vue.components()方法,里面传入2个参数,第1个是参数组件标签,第2个参数是一个组件构造器(也可以是选项对象,使用选项对象的template属性定义组件模板,使用这种方式Vue会自动地调用Vue.extend()方法)。其实所谓的语法糖就是直接选项对象来进行构造:

<div id="app">    <my-component></my-component></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    var myComponent = Vue.extend({        template:"<h1 style='color: red'>hello,Vue,js!</h1>"    });    Vue.component("my-component",myComponent);    new Vue({        el:"#app",    });</script>

上面是之前的方式,下面是使用语法糖的代码:

<div id="app">    <my-component></my-component></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">     Vue.component('my-component',{        template: "<p>This is the first component!</p>"        }    )      new Vue({        el:"#app",    });</script>

上面是使用注册语法糖实现的全局注册,接下来则是使用语法糖实现的局部注册:

<div id="app2">    <my-components2>    </my-components2>    <my-components3>    </my-components3></div><script type="text/javascript">    //在选项对象的components属性中实现局部注册:(必须在Vue实例内部进行局部注册)    new Vue({        el:"#app2",        components:{            // 局部注册,my-component2是标签名称            "my-components2":{                template:"<p>This is the second component!</p>",            },            // 局部注册,my-component3是标签名称            "my-components3":{                template:"<p>This is the third component!</p>",            }        }    })</script>

运行结果:


细心的你会发现无论是否使用语法糖,局部注册都必须在Vue实例的内部进行,且在components属性中实现局部注册。

模板分离

看到这里肯定会发现我们在template中引入了html代码,这就好像是Java中的JSP一样,这种方式会导致html和jS的高度耦合,不利于代码的扩展,同时降低了代码的可读性。

针对这个情况,Vue提供了三种解决方案:第一:使用<script>标签;第二:使用<template>标签;第三:使用定义组件的.vue格式文件(此处不介绍其用法)。

第一,使用<script>标签,如下述代码:

<body><div id="app">    <my-component></my-component></div><script type="text/x-template" id="myComponent"><p>This is a component used script!</p></script><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    Vue.component("my-component",{        template:"#myComponent",     });    new Vue({        el:"#app"    })</script></body>

此时template选项对象对应的值不再是HTML元素,而是一个id选择器,此时Vue.js会根据这个id查找对应的元素,然后将改元素内的HTML作为模板进行编译。这种方式其实很好理解,因为JS善于使用选择器来进行元素的选择、定位、修改等操作。

注意:使用<script>标签时,必须指定type类型为text/x-template,目的就是告诉浏览器这不是一段js脚本,那么浏览器在解析HTML文档时会忽略<script>标签内定义的内容。

第二:使用<template>标签,代码如下:

<body><div id="app">    <my-component></my-component></div><template id="templateMyComponent">    <p>This is a component used template!</p></template><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    Vue.component("my-component",{        template:"#templateMyComponent",     });    new Vue({        el:"#app"    })</script></body>

使用template标签很容易使人想到这是模板信息,因此不需要再指定type类型了。个人建议使用template标签,因为可读性非常好。

组件的el和data对象选项

前面我们曾多次在Vue实例中使用el和data对象选项,比如:

    new Vue({        el:"#app2",        data: {            name: "envy",            age: 23,        }    })

这个没什么需要介绍的,不过它们两个特殊在于定义组件选项对象时,data和el选项对象值必须使用函数!

其实就是说:其他的参数对象在 Vue.extend() 或Vue.component()中可以传入对象,而data和el参数对象只能是函数。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    Vue.component("my-component",{        template:"#templateMyComponent",         data:{            name:"envy"        }    });</script>

运行时,后台会报错:

vue.js:634 [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

即data应该是一个函数,用于在组件定义中返回每个实例的值。

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    Vue.component("my-component",{        template:"#templateMyComponent",         data:function(){        return {name:"envy"}    }    });</script>

props的使用

由于各个组件实例的作用域是独立的,因此想在子组件中使用父组件的数据似乎是难以实现的,此时props就派上用场了。

props入门

举一个非常简单的例子,子组件如何通过props来获取父组件的数据。

先定义一个Vue的实例,且将子组件my-component进行局部注册,同时定义Vue实例的data选项属性值,如下代码所示:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    var vm = new Vue({        el: "#app",        data: {            name: "xiaoming",            age: 28,        },        components: {            "my-component": {                template: "#mytemplate",                props: ["myName", "myAge"],            }        }    })</script>

注意,如果我们想获取父组件的数据,那么必须提前在子组件中定义props属性,也就是代码中的props: ['myName', 'myAge']。便于理解可以将Vue的实例作为my-component的父组件,那么我们想要的则是在子组件中输出name: "xiaoming",age: 28。

接着定义子组件的html模板:

<template id="mytemplate">    <table>        <tr>            <th colspan="2">子组件数据</th>        </tr>        <tr>            <td>myName</td>            <td>{{ myName }}</td>        </tr>        <tr>            <td>myAge</td>            <td>{{ myAge }}</td>        </tr>    </table></template>

最后也是最重要的一步,将父组件的数据通过props传递给子组件:

<div id="app">    <my-component v-bind:my-name="name" v-bind:my-age="age"></my-component></div>

注意:在子组件中定义props时,使用了驼峰命名法进行变量命名。由于html不区分大小写,故使用驼峰命名法命名的props用于特性时,需要转为 短横线隔开的形式。在本例中props定义的myName,在用作特性时需要转换为my-name这种形式。

运行结果:

总结:在父组件中使用子组件时,可通过以下语法将数据传递给子组件(注意可使用多个v-bind指令):

<child-component v-bind:子组件props="父组件数据属性"></child-component>

props单向数据绑定

前面父组件可以将数据传递给子组件,那么现在有一个问题就是假设子组件修改了数据,那么相应的父组件数据是否也发生了改变?

同样是先定义一个Vue的实例,且将子组件my-component进行局部注册,同时定义Vue实例的data选项属性值,如下代码所示:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script type="text/javascript">    var vm = new Vue({        el: "#app",        data: {            name: "xiaoming",            age: 28,        },        components: {            "my-component": {                template: "#mytemplate",                props: ["myName", "myAge"],            }        }    })</script>

接着定义子组件的html模板:

<template id="mytemplate">    <table>        <tr>            <th colspan="3">子组件数据</th>        </tr>        <tr>            <td>myName</td>            <td>{{ myName }}</td>            <td><input type="text" v-model="myName"></td>        </tr>        <tr>            <td>myAge</td>            <td>{{ myAge }}</td>            <td><input type="text" v-model="myAge"></td>        </tr>    </table></template>

然后为了展示父组件的数据是否发生改变,则需要将数据进行展示:

<div id="app">    <table>        <tr>            <th colspan="3">父组件数据</th>        </tr>        <tr>            <td>Name</td>            <td>{{ name }}</td>            <td><input type="text" v-model="name"></td>  <!--将name绑定到文本框-->        </tr>        <tr>            <td>Age</td>            <td>{{ age }}</td>            <td><input type="text" v-model="age"></td>   <!--将age绑定到文本框-->        </tr>    </table></div>

最后也是最重要的一步,将父组件的数据通过props传递给子组件(注意代码必须放到<div id="app"></div>之间):

<my-component v-bind:my-name="name" v-bind:my-age="age"></my-component>

运行结果:(由于无法获得动图,此处是静态图片)

结果是修改子组件的数据,父组件的信息不会发生变化。但是修改了父组件的数据,则子组件得数据也会发生变化,这个很好理解。为什么会有这种单向的数据绑定机制呢?其实是防止子组件的随意修改,导致父组件状态发生改变。props默认是单向数据绑定!有了单向的数据绑定肯定也有双向的数据绑定,对是的,因为确实在某些场景下需要联动,一个页面发生变化另一个页面也需要对这个变化作出响应。关于props双向数据绑定的介绍将会在后续文章中进行分享。