本文的Demo和源代码已放到GitHub,如果您觉得本篇内容不错,请点个赞,或在GitHub上加个星星!

https://github.com/zwl-jasmine95/Vue_test

 以下所有知识都是基于vue.js 2.0版本


 

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

一、组件的创建与注册

1、全局组件

组件的使用有两个步骤:注册组件  使用组件。

vue.js学习之组件(上篇)

(1)要注册一个全局组件,可以使用 :

Vue.component(tagName, options)

例如:

Vue.component('my-component', {
  // 选项
})

 (2)创建根实例:

//创建根实例
 var vm = new Vue({
        el:'#component-demo'
 });

案例:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>全局组件(component)的基本使用</title>
 6 </head>
 7 <body>
 8     <div id="component-demo">
 9         <!-- 2. #component-demo是Vue实例挂载的元素,应该在挂载元素范围内使用组件-->
10         <hello-component></hello-component>
11     </div>
12 
13 <script type="text/javascript" src="../lib/js/vue.js"></script>
14 <script type="text/javascript">
15 
16     //1.全局组件注册,并指定组件的标签,组件的HTML标签为<hello-component>
17     Vue.component('hello-component',{
18         template:'<h1>hello component!</h1>'
19     });
20 
21     //创建根实例
22     var vm = new Vue({
23         el:'#component-demo'
24     });
25 
26 </script>
27 </body>
28 </html>

vue.js学习之组件(上篇)

 

注意事项:

  1. 对于自定义标签名,Vue.js 不强制要求遵循 W3C 规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好。
  2. 注册组件必须发生在根实例初始化前。
  3. 如果自定义标签名使用的是驼峰式命名,在使用的时候仍然要在大写字母处加上短杠,并将大写字母改为小写。例如:命名为‘helloWorldComponent’,在使用时变为<hello-world-component></hello-world-component>

 

2、局部组件

 调用Vue.component()注册组件时,组件的注册是全局的,这意味着该组件可以在任意Vue示例下使用。
如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的components属性实现局部注册

 1     <div id="component-demo">
 2         <local-component></local-component>
 3     </div>
 4     <script type="text/javascript">
 5     
 6         var child = {
 7             template:'<h1>局部组件的基本使用!</h1>'
 8         };
 9     
10         var vm = new Vue({
11             el:'#component-demo',
12             components:{
13                 'local-component':child
14             }
15         });
16     </script>

由于local-component组件是注册在#component-demo元素对应的Vue实例下的,所以它不能在其它Vue实例下使用。如果你这样做了,浏览器会提示一个错误:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>局部组件</title>
 6 </head>
 7 <body>
 8 
 9     <div id="component-demo">
10         <local-component></local-component>
11     </div>
12 
13     <div id="component-demo2">
14         <!--这里会报错,因为local-component是#component-demo的局部组件,不能在其他地方使用-->
15         <local-component></local-component>
16     </div>
17 
18 <script type="text/javascript" src="../lib/js/vue.js"></script>
19 <script type="text/javascript">
20 
21     var child = {
22         template:'<h1>局部组件的基本使用!</h1>'
23     };
24 
25     var vm = new Vue({
26         el:'#component-demo',
27         components:{
28             'local-component':child
29         }
30     });
31 
32     var vm2 = new Vue({
33         el:'#component-demo2'
34     });
35 
36 
37 </script>
38 </body>
39 </html>

vue.js学习之组件(上篇)

 组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。

在 Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。

vue.js学习之组件(上篇)

 

 


 

二、组件的DOM模板解析说明

 当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。尤其像这些元素 <ul><ol><table><select> 限制了能被它包裹的元素,而一些像 <option> 这样的元素只能出现在某些其它元素内部。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
  <my-row>...</my-row>
</table>

自定义组件 <my-row> 被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:

<table>
  <tr is="my-row"></tr>
</table>

应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

  • <script type="text/x-template">
  • JavaScript 内联模版字符串
  • .vue 组件

因此,有必要的话请使用字符串模版。

 


 

三、组件字符串模板

上述组件注册方法在template选项中拼接HTML元素比较麻烦,这也导致了HTML和JavaScript的高耦合性。庆幸的是,Vue.js提供了两种方式将定义在JavaScript中的HTML模板分离出来。

 1、使用 script 标签

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>使用script标签</title>
 6 </head>
 7 <body>
 8 <div id="demo">
 9     <my-component></my-component>
10 </div>
11 
12 <script type="text/x-template" id="myComponent">
13     <h1>组件使用之使用script标签</h1>
14 </script>
15 
16 <script type="text/javascript" src="../lib/js/vue.js"></script>
17 <script type="text/javascript">
18     Vue.component('my-component',{
19         template:'#myComponent'
20     });
21 
22     var vm = new Vue({
23         el:'#demo'
24     });
25 </script>
26 </body>
27 </html>

vue.js学习之组件(上篇)

template选项现在不再是HTML元素,而是一个id,Vue.js根据这个id查找对应的元素,然后将这个元素内的HTML作为模板进行编译。

 vue.js学习之组件(上篇)

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

 

 2、使用 template 标签

 如果使用<template>标签,则不需要指定type属性。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>使用template标签</title>
 6 </head>
 7 <body>
 8 <div id="demo">
 9     <my-component></my-component>
10 </div>
11 
12 <template id="myComponent">
13     <h1>组件使用之使用template标签</h1>
14 </template>
15 
16 <script type="text/javascript" src="../lib/js/vue.js"></script>
17 <script type="text/javascript">
18     Vue.component('my-component',{
19         template:'#myComponent'
20     });
21 
22     var vm = new Vue({
23         el:'#demo'
24     });
25 </script>
26 </body>
27 </html>

vue.js学习之组件(上篇)

vue.js学习之组件(上篇)

 

建议使用<script>或<template>标签来定义组件的HTML模板。这使得HTML代码和JavaScript代码是分离的,便于阅读和维护。

 


 

四、组件data选项的限制

通过 Vue 构造器传入的各种选项大多数都可以在组件里用。data 是一个例外,它必须是函数。

如果这样写:

Vue.component('hello-component',{
        template:'<h1>{{message}}</h1>',
        data:{
            message:1
        }
});

会报错:

vue.js学习之组件(上篇)

 

 所以应该改为:

Vue.component('my-component',{
        template:'<h1>{{message}}</h1>',
        data:function () {
            return {message : 'data必须为函数'};
        }
 });

 


 

五、props—父组件向子组件传递信息

组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的 props 选项。

 1、静态数据

 1    <div id="demo">
 2         <child-component message="hello props!"></child-component>
 3     </div>
 4 
 5     <template id="myComponent">
 6         <h1>{{message}}</h1>
 7     </template>
 8 
 9     <script type="text/javascript" src="../lib/js/vue.js"></script>
10     <script type="text/javascript">
11         var child = {
12             // 声明 props
13             props: ['message'],
14             // 就像 data 一样,prop 可以用在模板内
15             // 同样也可以在 vm 实例中像“this.message”这样使用
16             template:'#myComponent'
17         };
18 
19         var vm = new Vue({
20             el:'#demo',
21             components:{
22                 'child-component':child
23             }
24         });
25     </script>

HTML 特性是不区分大小写的。所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名

 

 2、动态数据

在模板中,要动态地绑定父组件的数据到子模板的 props,与绑定到任何普通的HTML特性相类似,就是用 v-bind。每当父组件的数据变化时,该变化也会传导给子组件。

 1 <body>
 2     <div id="demo">
 3         父组件信息:<input type="text" v-model="parentMessage">
 4         <child-component v-bind:message="parentMessage"></child-component>
 5     </div>
 6 
 7     <template id="myComponent">
 8         <h1>子组件获取的信息为:{{message}}</h1>
 9     </template>
10 
11     <script type="text/javascript" src="../lib/js/vue.js"></script>
12     <script type="text/javascript">
13 
14         var child = {
15             props: ['message'],
16             template:'#myComponent'
17         };
18 
19         var vm = new Vue({
20             el:'#demo',
21             data:{
22                 parentMessage:'这里是父组件信息!'
23             },
24             components:{
25                 'child-component':child
26             }
27         });
28     </script>
29 </body>

vue.js学习之组件(上篇)

 

demo

初学者常犯的一个错误是使用字面量语法传递数值:

<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>

因为它是一个字面 prop,它的值是字符串 "1" 而不是 number。如果想传递一个实际的 number,需要使用 v-bind,从而让它的值被当作 JavaScript 表达式计算:

<!-- 传递实际的 number -->
<comp v-bind:some-prop="1"></comp>

3、props的绑定类型—单项绑定

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>props单项数据绑定</title>
 6     <link rel="stylesheet" href="../lib/css/bootstrap.min.css">
 7 </head>
 8 <body>
 9     <div id="demo">
10         <table class="table table-striped">
11             <tr>
12                 <td colspan="3">父组件数据</td>
13             </tr>
14             <tr>
15                 <td>姓名</td>
16                 <td>{{parentName}}</td>
17                 <td><input type="text" v-model="parentName"/></td>
18             </tr>
19             <tr>
20                 <td>年龄</td>
21                 <td>{{parentAge}}</td>
22                 <td><input type="text" v-model="parentAge"/></td>
23             </tr>
24         </table>
25 
26         <!--注意这里camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名-->
27         <child-component v-bind:child-name="parentName" v-bind:child-age="parentAge"></child-component>
28 
29     </div>
30 
31     <template id="myComponent">
32         <table class="table table-striped">
33             <tr>
34                 <td colspan="3">子组件数据</td>
35             </tr>
36             <tr>
37                 <td>姓名</td>
38                 <td>{{childName}}</td>
39             </tr>
40             <tr>
41                 <td>年龄</td>
42                 <td>{{childAge}}</td>
43             </tr>
44         </table>
45     </template>
46 
47     <script type="text/javascript" src="../lib/js/vue.js"></script>
48     <script type="text/javascript">
49 
50         var child = {
51             template:'#myComponent',
52             props: ['childName','childAge']
53 
54         };
55 
56         var vm = new Vue({
57             el:'#demo',
58             data:{
59                 parentName:'*茉莉花开*',
60                 parentAge:22
61             },
62             components:{
63                 'child-component':child
64             }
65         });
66     </script>
67 </body>
68 </html>

vue.js学习之组件(上篇)

父组件的数据更改时,子组件的数据也跟着修改:

vue.js学习之组件(上篇)

 

每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

为什么我们会有修改 prop 中数据的冲动呢?通常是这两种原因:

  1. prop 作为初始值传入后,子组件想把它当作局部数据来用;

  2. prop 作为初始值传入,由子组件处理成其它数据输出。

对这两种原因,正确的应对方式是:

  1. 定义一个局部变量,并用 prop 的值初始化它:

    props: ['initialCounter'],
    data: function () {
        return { counter: this.initialCounter }
    }
  2. 定义一个计算属性,处理 prop 的值并返回。

    props: ['size'],
    computed: {
        normalizedSize: function () {
            return this.size.trim().toLowerCase()
        }
    }

针对上述demo代码,我们可以稍作修改,将子组件从父组件获取的姓名变为新的姓名:

vue.js学习之组件(上篇)

我们可以看到效果:

vue.js学习之组件(上篇)

!!!在这里,父组件的姓名改变时,子组件的姓名不会跟着改变。

demo

注意:

1.在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

2.Vue 2.x相比较Vue 1.x而言,升级变化除了实现了Virtual-Dom以外,给使用者最大不适就是移除的组件的props的双向绑定功能。以往在Vue1.x中利用propstwoWay.sync绑定修饰符就可以实现props的双向绑定功能,但是在Vue2中彻底废弃了此功能,如果需要双向绑定需要自己来实现。

4、props验证

 我们可以为组件的 props 指定验证规格。如果传入的数据不符合规格,Vue 会发出警告。当组件给其他人使用时,这很有用。

 要指定验证规格,需要用 对象的形式,而不能用字符串数组:

Vue.component('example', {
  props: {

    // 基础类型检测 (`null` 意思是任何类型都可以)
    propA: Number,

    // 多种类型
    propB: [String, Number],

    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },

    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },

    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },

    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

type 也可以是一个自定义构造器函数,使用 instanceof 检测。

当 prop 验证失败,Vue 会在抛出警告 (如果使用的是开发版本)。注意 props 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 datacomputed 或 methods 等实例属性还无法使用。

 

 实例:将第三小节中的demo代码修改,给传递的数据姓名和年龄加上验证,姓名必须为字符串类型,年龄必须为数字且不为空:

vue.js学习之组件(上篇)

在年龄那输入“m”,可以看见控制台报错了。因为传递的数据是字符串类型,而年龄props验证要求的是数字。

vue.js学习之组件(上篇)

 demo


六、组件自定义事件

父组件是使用 props 传递数据给子组件,但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。

 1、使用 v-on 绑定自定义事件

每个 Vue 实例都实现了事件接口 (Events interface),即:

  • 使用 $on(eventName) 监听事件
  • 使用 $emit(eventName) 触发事件

另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。不能用 $on 侦听子组件抛出的事件,而必须在模板里直接用 v-on 绑定。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>v-on绑定自定义事件</title>
 6     <script type="text/javascript" src="../lib/js/vue.js"></script>
 7 </head>
 8 <body>
 9     <div id="counter-event-example">
10         <p>{{ total }}</p>
11         <button-counter v-on:add="incrementTotal"></button-counter>
12     </div>
13 
14     <script type="text/javascript">
15 
16         Vue.component('button-counter', {
17             template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
18             data: function () {
19                 return {
20                     counter: 0
21                 }
22             },
23             methods: {
24                 incrementCounter: function () {
25                     this.counter += 1;
26                     this.$emit('add'); //向父组件报告,自己发生了‘add’事件
27                 }
28             }
29         });
30         new Vue({
31             el: '#counter-event-example',
32             data: {
33                 total: 0
34             },
35             methods: {
36                 incrementTotal: function () {
37                     this.total += 1
38                 }
39             }
40         })
41     </script>
42 </body>
43 </html>
View Code

相关文章:

  • 2022-12-23
  • 2021-07-19
  • 2021-06-02
  • 2022-12-23
  • 2021-09-11
  • 2022-12-23
  • 2021-09-02
猜你喜欢
  • 2021-06-19
  • 2021-11-28
  • 2021-09-08
  • 2022-12-23
  • 2021-11-11
  • 2021-05-03
相关资源
相似解决方案