组件是可复用的 Vue 实例,且带有一个名字,可以把这个组件作为自定义元素来使用,组件化与模块化的功能类似,区别在于:
- 模块化是从代码逻辑的角度划分,方便代码分层,保证每个功能模块的职能单一;
- 组件化是从UI界面的角度划分,前端的组件化,是为了方便UI组件的重用。
一、组件注册
1、全局组件注册
1.1 Vue.extend() 配合 Vue.component()
使用基础 Vue 构造器,创建一个组件模板对象,传入到 Vue.component() 中。
var my-component-name = Vue.extend({
// 通过 template 属性指定了要展示的HTML结构
template: 'HTML元素'
})
Vue.component('my-component-name', my-component-name)
1.2 直接使用Vue.component()
直接向 Vue.component() 中传入一个字面量对象。
Vue.component("my-component", {
template: '<p>This is a content.</p>'
})
1.3 在 <template> 标签中定义模板字符串
在被Vue实例控制的区域外,使用 <template> 元素定义组件的HTML模板结构,然后将元素id传入到 template 属性中。
<div id="app">
<!-- 组件使用 -->
<my-com></my-com>
</div>
<template id="my-com">
<p>This is a content.</p>
</template>
<script>
Vue.component("my-com", {
template: '#my-com'
})
var vm = new Vue({
el: "#app",
})
</script>
2、局部组件注册
可以在Vue实例内,定义局部组件。
var vm = new Vue({
el: "#app",
components: {
'my-com': {
template: '<p>This is a content.</p>'
}
}
})
注意:
- 如果组件名称使用了大写字母,在使用时需要将大写字母替换为 ‘-小写字母’,例如组件名称 ‘myCom’ ,在使用时的名称为 ‘my-com’;
- 组件的 template 属性指向的模板内容,必须有一个唯一的根元素;
二、组件属性
组件与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等(el 选项是根实例特有的)。特别地,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
</head>
<body>
<div id="app">
<counter></counter>
<counter></counter>
</div>
<template id="counter">
<!-- 注意:这里外部需有个div容器,为了保证根元素唯一 -->
<div>
<input type="button" value="+1" @click="add">
<p>{{ count }}</p>
</div>
</template>
<script>
Vue.component("counter", {
template: '#counter',
// data属性是个函数,且返回值为对象
data: function(){
return {count: 0}
},
methods: {
add: function(){
this.count++
}
}
})
var vm = new Vue({
el: "#app",
})
</script>
</body>
</html>
三、动态组件
不同组件之间可以进行动态切换。
1、指令控制切换
可以通过条件指令 v-if、v-else ,来控制组件的显示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
</head>
<body>
<div id="app">
<a href="" @click.prevent="flag=true">登录</a>
<a href="" @click.prevent="flag=false">注册</a>
<login v-if="flag"></login>
<register v-else="flag"></register>
</div>
<template id="login">
<p>登录组件</p>
</template>
<template id="register">
<p>注册组件</p>
</template>
<script>
Vue.component("login", {
template: '#login'
})
Vue.component("register", {
template: '#register'
})
var vm = new Vue({
el: "#app",
data: {
flag: true
}
})
</script>
</body>
</html>
2、is 特性实现切换
Vue 的 <component> 元素中一个特殊的 is 特性可以实现组件切换,currentTabComponent 可以包括:
- 已注册组件的名字;
- 一个组件的选项对象;
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
- 示例代码:
<div id="app">
<a href="" @click.prevent="comName='login'">登录</a>
<a href="" @click.prevent="comName='register'">注册</a>
<!-- 由变量传入组件名称 -->
<component :is="comName"></component>
</div>
3、组件切换过渡
多个组件的过渡不需要使用 key 特性,只需要使用动态组件:
<!-- mode设置动画模式,这里设置为先出再进模式 -->
<transition mode="out-in">
<component :is="comName"></component>
</transition>
四、组件传值
1、传属性值
prop 是你可以在组件上注册的一些自定义特性,可以使用 v-bind 来动态传递 prop。子组件默认无法访问父组件的 data 数据,此时,我们可以通过 prop 向子组件传值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
</head>
<body>
<div id="app">
<!-- 父组件传值 -->
<login :msg="msg"></login>
</div>
<template id="login">
<p>{{ msg }}</p>
</template>
<script>
Vue.component("login", {
template: '#login',
// 父组件传入的值的名称需在prop中声明
props: ['msg']
})
var vm = new Vue({
el: "#app",
data: {
msg: 'a message form vm'
}
})
</script>
</body>
</html>
当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性,我们能够在组件实例中访问这个值,就像访问 data 中的值一样。组件中的 data 数据和 prop 数据区别在于,prop 中的属性值是只可读的且是由父组件传入的。
2、传方法值
可以通过自定义事件向子组件传递方法,我们通过事件绑定机制向自定义事件绑定方法,子组件就可以通过 “this.$emit(‘myevent’) ”调用传递过来的方法。
注意,v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。
<!-- 父组件传方法 -->
<login @myShow="show"></login>
Vue.component("login", {
template: '#login',
methods: {
myShow: function(){
// 注意,即使事件绑定时的名称含大写,这里也需要小写
this.$emit('myshow')
}
}
})
在子组件调用父组件的方法时,我们可以通过函数传参的方式,从子组件向父组件传值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
</head>
<body>
<div id="app">
<!-- 父组件传方法 -->
<login @myShow="show"></login>
</div>
<template id="login">
<input type="button" value="show" @click="myShow">
</template>
<script>
Vue.component("login", {
template: '#login',
data: function(){
return {sonMsg: 'message from son component'}
},
methods: {
myShow: function(){
// 注意,即使事件绑定时的名称含大写,这里也需要小写
// 通过函数传参向父组件传值
this.$emit('myshow', this.sonMsg)
}
}
})
var vm = new Vue({
el: "#app",
data: {
parentMsg: null
},
methods: {
show: function(msg){
// 接收子组件传递的值
this.parentMsg = msg
console.log(this.parentMsg)
}
}
})
</script>
</body>
</html>
五、获取组件引用
JavaScript 可以通过 ref 特性为子组件赋予一个 ID 引用,以便直接访问这个子组件。Vue实例中的 vm.$refs 对象,持有注册过 ref 特性 的所有 DOM 元素和组件实例。
$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
</head>
<body>
<div id="app">
<login ref="myLogin"></login>
<input type="button" value="获取组件" @click="show">
</div>
<template id="login">
<div></div>
</template>
<script>
Vue.component("login", {
template: '#login',
data: function(){
return {msg: 'message from component'}
}
})
var vm = new Vue({
el: "#app",
methods: {
show: function(){
console.log(this.$refs.myLogin.msg)
}
}
})
</script>
</body>
</html>
六、综合案例
一个具有发表评论功能的案例,我们将发表评论的UI区域做成组件,这里还用到了 localStorage 来对评论列表进行缓存。
只读的 Window.localStorage 属性允许你访问一个Document 源(origin)的对象 Storage,且存储的数据将保存在浏览器会话中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="../vue.min.js"></script>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app">
<comment-box @func="loadComments"></comment-box>
<ul class="list-group">
<li class="list-group-item" v-for="item in list" :key="item.id">
<span class="badge">评论人:{{ item.user }}</span>
{{ item.content }}
</li>
</ul>
</div>
<template id="comment-box">
<div>
<div class="form-group">
<label>评论人:</label>
<input type="text" class="form-control" v-model="user">
</div>
<div class="form-group">
<label>评论内容:</label>
<textarea type="text" class="form-control" v-model="content"></textarea>
</div>
<div class="form-group">
<input type="button" value="发表评论" class="btn btn-primary" @click="postComment">
</div>
</div>
</template>
<script>
var vm = new Vue({
el: "#app",
data: {
list: []
},
components: {
'comment-box': {
template: "#comment-box",
data: function(){
return {user: '', content: ''}
},
methods: {
postComment: function(){
// 0、判断输入是否有效
if(this.user == '' && this.content == '') return
// 1、构造评论对象
var comment = {id: Date.now(), user: this.user, content: this.content}
// 2、从localStorage获取评论列表,考虑初始情况
var list = JSON.parse(localStorage.getItem('cmts') || '[]')
// 3、将新评论对象加入列表
list.unshift(comment)
// 4、更新localStorage
localStorage.setItem('cmts', JSON.stringify(list))
// 5、清空输入框
this.user = this.content = ''
// 6、刷新列表
this.$emit('func')
}
}
}
},
methods: {
// 自动加载本地的评论列表
loadComments: function(){
this.list = JSON.parse(localStorage.getItem('cmts') || '[]')
}
},
created(){
this.loadComments()
}
})
</script>
</body>
</html>
参考链接: