参考菜鸟教程
一、简介
- 组件(Component)是 Vue.js 最强大的功能之一。
- 组件可以扩展 HTML 元素,封装可重用的代码。
- 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件
注册一个全局组件语法格式如下:
Vue.component(tagName, options)
tagName 为组件名,options 为配置选项。注册后,我们可以使用以下方式来调用组件:
<tagName></tagName>
注册组件的基本步骤
1.Vue.extend():
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
2.Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器
3.组件必须挂载在某个Vue实例下,否则它不会生效。
我们来看下面我使用了三次
而第三次其实并没有生效:
注册组件语法糖
在上面注册组件的方式,可能会有些繁琐。
Vue为了简化这个过程,提供了注册的语法糖。
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。
模板的分离写法
如果写成变量形式的话,无法自动对齐,并且比较乱。Vue提供了两种方案来定义HTML模块内容:
使用<script>标签
使用<template>标签
二、示例、全局组件
例如下面设置一个名称为wangxiaoyu的自定义全局组件,在script中Vue.component('wangxiaoyu', { template: '<h1>自定义组件!</h1>' })
方法进行注册,然后在html中直接<wangxiaoyu></wangxiaoyu>
使用
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<wangxiaoyu></wangxiaoyu>
</div>
<script>
// 注册
Vue.component('wangxiaoyu', {
template: '<h1>自定义组件!</h1>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>
</body>
{% endverbatim %}
</html>
访问页面
三、示例、局部组件
在vue对象中使用components: { 'wangxiaoyu': { template: '<h1>自定义组件!</h1>' } }
进行注册,然后在html中直接<wangxiaoyu></wangxiaoyu>
使用,这个组件只能在这个实例中使用。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<wangxiaoyu></wangxiaoyu>
</div>
<script>
// 创建根实例
new Vue({
el: '#app',
components: {
// <wangxiaoyu> 将只在父模板可用
'wangxiaoyu': {
template: '<h1>自定义组件!</h1>'
}
}
})
</script>
</body>
{% endverbatim %}
</html>
访问页面
四、组件数据存放
1、测试是否能直接访问实例中的data
组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
我们先来测试一下,组件中能不能直接访问Vue实例中的data
我们发现不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例中,Vue实例就会变的非常臃肿。
结论:Vue组件应该有自己保存数据的地方。
2、组件数据的存放
组件对象也有一个data属性(也可以有methods等属性,下面我们有用到)
只是这个data属性必须是一个函数
而且这个函数返回一个对象,对象内部保存着数据。
在data函数中返回的数据变量,可以在组件的template中进行使用
3、为什么组件data是一个函数呢
首先,如果不是一个函数,Vue直接就会报错。
其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
五、父组件和子组件
vue实例可以看做是最顶层的一个root父组件
1、基础实例1
子组件在父组件中注册,而不是在vue实例或全局中注册,并且在父组件的template中可以使用子组件,并且子组件要写在父组件之前。
如果子组件要在其他组件使用,需要在其他组件中注册或者在全局注册,否则只能在它注册的父组件中使用。
再往上,其实parent-cpn这个组件同时也是app这个vue实例的子组件
2、父传子 Prop自定义属性
父组件通过props向子组件传递数据
子组件通过事件向父组件发送消息
props的值有两种方式:
方式一:字符串数组,数组中的字符串就是传递时的变量名称。
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。
示例1
props: [‘message’]为子组件增加了message这个属性变量,也就是说,子组件中存在了message变量,可以在使用组件时直接message="hello!"用字符串给该属性赋值,也可以使用在使用组件时v-bind:message="xxx"来为这个属性变量赋值,这里的xxx也是个变量,是父组件data中的变量。
示例2
如下使用props: ['message']
定义子组件中的message属性变量,然后在template: '<span>{{ message }}</span>'
使用message属性,在html代码中使用<child message="hello!"></child>
直接赋值message变量。将vue实例作为父组件,其中包含子组件来简化代码。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<child message="hello!"></child>
</div>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 “this.message” 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app'
})
</script>
</body>
{% endverbatim %}
</html>
访问页面
示例3、动态 Prop
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件。
如下使用props: ['message']
定义子组件中的message属性,然后在template: '<span>{{ message }}</span>'
使用message属性,在html代码中使用<child v-bind:message="parentMsg"></child>
,用 v-bind 动态绑定 props 的message属性值和父组件的parentMsg变量,然后在父组件中定义parentMsg数据值data: { parentMsg: '父组件内容' }
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<div>
<input v-model="parentMsg">
<br>
<child v-bind:message="parentMsg"></child>
</div>
</div>
<script>
// 注册
Vue.component('child', {
// 声明 props
props: ['message'],
// 同样也可以在 vm 实例中像 "this.message" 这样使用
template: '<span>{{ message }}</span>'
})
// 创建根实例
new Vue({
el: '#app',
data: {
parentMsg: '父组件内容'
}
})
</script>
</body>
{% endverbatim %}
</html>
访问页面
这里将input<input v-model="parentMsg">
绑定了,所以修改input中的内容会跟着变化
示例4、使用 v-bind 指令将 todo 传到每一个重复的组件中
注意: prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<ol>
<todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
</ol>
</div>
<script>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
new Vue({
el: '#app',
data: {
sites: [
{ text: 'Runoob' },
{ text: 'Google' },
{ text: 'Taobao' }
]
}
})
</script>
</body>
{% endverbatim %}
</html>
访问页面
prop数据验证
在前面,我们的props选项是使用一个数组。
除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。
六、子传父 组件 - 自定义事件,例如点击事件
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!
我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
-
父组件中使用 v-on(eventName) 监听事件
-
子组件中使用 $emit(eventName) 触发事件
-
子组件先提交事件 this.$emit(“事件名称”,值)
-
在父组件中给子组件绑定事件 <child @事件名称=“父亲处理的方法”>
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。
如下
- 定义
button-counter
这个组件,template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
- 点击响应click事件执行
incrementHandler
方法,使用counter
变量,counter默认设为0function () { return { counter: 0 } }
,每点击一下则调用incrementHandler
方法进行加一 - 然后在使用组件的时候绑定increment事件,执行
incrementTotal
方法,表示点击的时候将total变量也加一。所以每点击一下都会调用incrementHandler和incrementTotal
说不太清楚就这样吧。。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src={% static "vue.js" %}></script>
</head>
{% verbatim %}
<body>
<div id="app">
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
</div>
<script>
Vue.component('button-counter', {
template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementHandler: function () {
this.counter += 1
//在子组件的点击方法incrementHandler中执行increment事件,该事件会再去执行incrementTotal方法
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
</script>
</body>
{% endverbatim %}
</html>
访问页面并点击,可以看到上面的值为下面两个相加,每点一次则加一
data 必须是一个函数
上面例子中,可以看到 button-counter 组件中的 data 不是一个对象,而是一个函数,这样的好处就是每个实例可以维护一份被返回对象的独立的拷贝,如果 data 是一个对象则会影响到其他实例。
data: function () {
return {
count: 0
}
}
七、父子组件的访问方式
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用
c
h
i
l
d
r
e
n
或
children或
children或refs
子组件访问父组件:使用$parent
$children
this.$children是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的message状态。
$refs的使用:
$children
的缺陷:
通过$children
访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs
和ref指令通常是一起使用的。
首先,我们通过ref给某一个子组件绑定一个特定的ID。
其次,通过this.$refs.ID就可以访问到该组件了。
$parent
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
尽管在Vue开发中,我们允许通过$parent
来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过$parent
直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
八、插槽
slot基本使用
我们通过一个简单的例子,来给子组件定义一个插槽:
中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
具名插槽slot
当子组件的功能复杂时,子组件的插槽可能并非是一个。
比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
这个时候,我们就需要给插槽起一个名字只要给slot元素一个name属性即可
<slot name='myslot'></slot>