Vue入门实战教程(六)—— 组件基础

此学习教程是对官方教程的解析,

本章节主要涉及到的官方教程地址:

组件基础 — Vue.js

上一章 :Vue入门实战教程(五)—— 控制层:方法及过滤器

本章介绍组件基本知识, 大概了解组件是怎么回事。

组件构造和注册

组件构造

看过vue源码就可以知道Vue组件是VueComponent的实例,VueComponent类继承自Vue类,所以Vue组件本质是Vue类的实例,即Vue 实例。

VueComponent类是Vue组件构造器,通过调用Vue.extend( options )产生。其中构造选项options和Vue实例化的选项是一致的,只不是挂载元素选项el、外部属性赋值选项propsData必须在实例化中才可使用。构造选项和实例化选项都存在时,实例化选项会覆盖构造选项。

例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
<div id="app0">
</div>
<Br/>
<div id="app1">
</div>
<Br/>
<div id="app2">
</div>


<script>
var MsgVueComponent = Vue.extend({
  //必须是函数,组件数据必须是独立的拷贝
  data: function () {
    return {
      msg2: 'hello'
    }
  },
  //外部属性
  props: ['msg'],
  //组件模板
  template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
});

//第一个组件, MsgVueComponent的实例, 实例化选项会覆盖MsgVueComponent的构造选项
var comp1 = new MsgVueComponent({
  //组件也是Vue实例,可以挂载
  el: "#app0",
  //为外部属性msg赋值
  propsData: {
    msg: 'hello0'
  },
  //覆盖内部属性data
  data:{
    msg2: 'hi'
  },
  //覆盖构造器定义的模板
  template: '<div>comp0.msg:{{ msg }}<Br/> comp0.msg2:{{ msg2 }}</div>'
})

//第二个组件,一般用法,为外部属性msg赋值就行了。
var comp1 = new MsgVueComponent({
  el: "#app1",
  propsData: {
    msg: 'hello1'
  }
})

//第三个组件,创建组件实例并手动挂载
var comp2 = new MsgVueComponent({
  propsData: {
    msg: 'hello2'
  }
})
comp2.$mount("#app2");
</script>
</body>
</html>

组件注册

全局注册

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。

使用以下API:
Vue.component( id, [definition] )
第一个参数id:组件名称
第二个参数 [definition] :组件构造器,或者组件构造器的构造选项

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
<div id="app1">
	<msg-comp msg="hello1"></msg-comp>
</div>


<script>
var MsgVueComponentOptions = {
  //必须是函数,组件数据必须是独立的拷贝
  data: function () {
    return {
      msg2: 'hello'
    }
  },
  //外部属性
  props: ['msg'],
  //组件模板
  template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}

//第一种全局注册方法,第二个参数使用组件构造器的构造选项对象
Vue.component('msg-comp', MsgVueComponentOptions);

//第二种全局注册方法,第二个参数使用组件构造器
//Vue.component('msg-comp', Vue.extend(MsgVueComponentOptions));

var vm = new Vue({
	el: "#app1",
})
</script>
</body>
</html>

局部注册

局部注册的组件仅可以用在其被注册的 Vue 实例中使用,不包括其组件树中的子组件的模板中。

局部注册的方法:
在实例化选项components中设置组件名和组件构造器
{
components: {
//第一种局部注册方法,第二个参数使用组件构造器的构造选项对象
‘msg-comp’: MsgVueComponentOptions
//第二种全局注册方法,第二个参数使用组件构造器
//‘msg-comp’: Vue.extend(MsgVueComponentOptions)
}
}

例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
<div id="app1">
	<msg-comp msg="hello1"></msg-comp>
</div>

<script>
var MsgVueComponentOptions = {
  //必须是函数,组件数据必须是独立的拷贝
  data: function () {
    return {
      msg2: 'hello'
    }
  },
  //外部属性
  props: ['msg'],
  //组件模板
  template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}

//局部注册
var vm = new Vue({
	el: "#app1",
	components: {
		//第一种局部注册方法,第二个参数使用组件构造器的构造选项对象
		'msg-comp': Vue.extend(MsgVueComponentOptions)
		//第二种全局注册方法,第二个参数使用组件构造器
		//'msg-comp': Vue.extend(MsgVueComponentOptions)
	}
})
</script>
</body>
</html>

组件主要构造选项

我们看一下上面例子组件的构造选项:

var MsgVueComponentOptions = {
  //必须是函数,组件数据必须是独立的拷贝
  data: function () {
    return {
      msg2: 'hello'
    }
  },
  //外部属性
  props: ['msg'],
  //组件模板
  template: '<div>comp.msg:{{ msg }}<Br/>comp.msg2:{{ msg2 }}</div>'
}

如上所示,组件构造选项主要有以下三个:

内部属性:data

组件是可复用的,所以组件的内部属性data必须是独立的拷贝,不能相互影响。所以data必须是一个构造函数,返回一个新的data对象,而不是引用一个相同的data对象。

外部属性:props

props是父组件向子组件传递数据的方式,值可以是任何类型。Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。

三种传值方式:

以自定义 attribute赋值,非v-bind方式

<msg-comp msg="hello1"></msg-comp>

以自定义 attribute赋值,v-bind方式

<msg-comp v-bind:msg="msg"></msg-comp>
new Vue({ data: { msg: 'hello1' } })

组件实例化时对外部属性赋值

new MsgVueComponent({ propsData: { msg: 'hello1' } })

组件模板:template

模板约束

每个组件必须只有一个根元素。所以如果多个元素的话,模板的内容必须包裹在一个父元素中。

例如以下html

<h3>{{ title }}</h3>
<div v-html="content"></div>

必须转换为

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>
插槽

插槽的作用是将组件标签包含的内容嵌入到组件模板中。

例如组件注册如下:

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
})

组件使用:

<alert-box>
  Something bad happened.
</alert-box>

最终渲染成:

<div class="demo-alert-box">
      <strong>Error!</strong>
      Something bad happened.
</div>

可以看到,最终渲染结果是把组件标签包含的内容Something bad happened.嵌入到了组件模板<slot></slot>的位置。

那插槽最大的好处是什么呢?
回顾一下vue官方教程实战(二)——Vue 实例的例子:我们要创建一个包含下面这种树形结构Vue 实例的Vue应用:

根实例
└─ TodoList
   ├─ TodoItem
   └─ TodoListFooter

最终实现了如下效果:

<div id="app-7">
  <todo-list></todo-list>
</div>

虽然上面的实现在组件内部实现了树型结构, 但最理想的实现应该是这样的:

<div id="app-7">
  <todo-list>
	<todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"></todo-item>
	<todo-list-footer></todo-list-footer>
  </todo-list>
</div>

这在没有插槽slot是不可能的。现在有了插槽,我们可以重写这个例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
<div id="app-7">
  <todo-list>
	<todo-item v-for="item in groceryList" v-bind:todo="item" v-bind:key="item.id"></todo-item>
	<todo-list-footer></todo-list-footer>
  </todo-list>
</div>
<script>
//局部注册todoItem组件
var TodoItem = {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
}

//局部注册TodoListFooter组件
var TodoListFooter = {
  template: '<li>=======</li>'
}

//局部注册TodoListFooter组件
//导入TodoItem、TodoListFooter组件
var TodoList = {
  template: '<ol><slot></slot></ol>'
}

//创建名为app7的Vue应用
//导入TodoList组件
var app7 = new Vue({
  el: '#app-7',
  data: {
      groceryList: [
        { id: 0, text: '蔬菜' },
        { id: 1, text: '奶酪' },
        { id: 2, text: '随便其它什么人吃的东西' }
      ]
  },
  components: {
    'todo-list':TodoList,
	'todo-item':TodoItem,
    'todo-list-footer': TodoListFooter
  }
})
</script>
</body>
</html>
解析 DOM 模板时的注意事项

有些HTML 元素之间有一些严格的父子关系(即父元素内部严格限制可以出现哪些子元素,或子元素只能出现在某些特定的父元素内部)
例如
<ul>、<ol>对应<li>
<table>对应<tr>
<select>对应<option>

如上原因,如果我们从以下来源使用模板的话,会造成渲染错误:

  1. 使用挂载元素el的inner Html,即只有el,没有template和渲染函数
  2. 使用template: css选择器, 且不是选择<script type="text/x-template">的内容
  3. 使用渲染函数

比如下例,这个自定义组件<blog-post-row>会被作为无效的内容提升到外部,并导致最终渲染结果出错

<table>
  <blog-post-row></blog-post-row>
</table>

可以全用is attribute进行转化,意思是这不是普通DOM元素,而是Vue组件。

<table>
  <tr is="blog-post-row"></tr>
</table>

如果我们从以下来源使用模板的话,这条限制是不存在的:

  1. 字符串 (例如:template: ‘…’)
  2. 单文件组件 (.vue)
  3. <script type="text/x-template">

组件自定义事件

如下例如示:
在组件模板中的其中一个元素上使用$emit抛出事件
$emit的第一个参数’enlarge-text’是事件名,对应父元素blog-post上的v-on:enlarge-text
$emit的第二个参数0.1,对应父元素blog-post上的v-on:enlarge-text中的 $event
这也是子组件向父组件传递数据的一种方式

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
      v-on:enlarge-text="postFontSize += $event"
    ></blog-post>
  </div>
</div>

<script>
Vue.component('blog-post', {
  props: ['post'],
  template: '\
    <div class="blog-post">\
      <h3>{{ post.title }}</h3>\
      <button v-on:click="$emit(\'enlarge-text\', 0.1)">Enlarge text</button>\
      <div v-html="post.content"></div>\
    </div>\
  '
})


new Vue({
  el: '#blog-posts-events-demo',
  data: {
    posts: [
        { id: 0, title: '蔬菜' },
        { id: 1, title: '奶酪' },
        { id: 2, title: '随便其它什么人吃的东西' }
    ],
    postFontSize: 1
  }
})
</script>
</body>
</html>

组件切换(动态组件)

比如在一个多标签的界面里, 不同组件之间进行动态切换是非常有用的

基本语法

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

使用已注册组件的名字来切换组件

例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
    <input type="button" value="A组件" @click="currentTabComponent='a_component'"/>
    <input type="button" value="B组件" @click="currentTabComponent='b_component'"/>

    <!--创建动态组件 <component :is="组件名称"></component> -->
    <component :is="currentTabComponent"></component>
</div>

<script>
new Vue({
	el: '#example',
	data:{
		currentTabComponent:'a_component'
	},
	components:{
		a_component:{
			template:'<h1>我是A组件</h1>'
		},
		b_component:{
			template:'<h1>我是B组件</h1>'
		}
	}
})
</script>
</body>
</html>

使用组件的选项对象来切换组件

和上例同样效果的例子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
    <input type="button" value="A组件" @click="compName='A'"/>
    <input type="button" value="B组件" @click="compName='B'"/>

    <!--创建动态组件 <component :is="组件名称"></component> -->
    <component :is="currentTabComponent"></component>
</div>

<script>
new Vue({
	el: '#example',
	data:{
		compName:'A'
	},
	computed: {
		currentTabComponent: function () {
			return {template:'<h1>我是'+this.compName+'组件</h1>'};
		}
	}
})
</script>
</body>
</html>

本章节教程结束。

全部教程地址:Vue入门实战教程 | 寒于水学习网

下一章:Vue入门实战教程(七)—— 生命周期概述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值