什么是组件
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo' })
</script>
</body>
</html>
这些类似于
使用组件的好处:
提高开发效率
方便重复使用
简化调试步骤
提升整个项目的课维护性
便于协同开发
组件系统让我们可以用独立可复用的小组件来构建大型应用,
几乎任意类型的应用的界面都可以抽象为一个组件树,如图5-1所示
全局注册
全局注册有三种方式。
要注册一个全局组件,我们可以使用 Vue.component(tagName,options),代码如下:
Vue.component('my-component', {
// 选项
})
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component', {
template: '<h1>注册</h1>'
});
var vm=new Vue({
el:'#app'
})
</script>
使用 Vue.extend 配合 Vue.component 方法
<div id="app">
<my-list></my-list>
</div>
<script>
var list=Vue.extend({
template:'<h1>this is a list</h1>',
});
Vue.component("my-list",list);
//根实例
new Vue({
el:"#app",
})
</script>
将模板字符串,定义到 script 标签中
<div id="app">
<account></account>
</div>
<template id="tmpl">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</template>
<script>
Vue.component('account', {
template: '#tmpl'
});
new Vue({
el:"#app",
})
</script>
局部注册
如果不需要全局注册,或者是让组件使用在其它组件内,可以用选项对象的 components 属性实现局部注册
<div id="app">
<account></account>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {},
methods: {},
components: { // 定义子组件
account: { // account 组件
template: '<div><h1>这是Account组</h1><login></login></div>', // 在这里使用定义的子组件
components: { // 定义子组件的子组件
login: { // login 组件
template: "<h3>这是登录组件</h3>"
}
}
}
}
});
</script>
组件的基本使用
可以使用“flag”标识符结合“v-if”和“v-else”切换组件
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="button" value="toggle" @click="flag=!flag">
<account v-if="flag"></account>
<login v-else="flag"></login>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {},
components: { // 定义子组件
account: { // account 组件
template: '<div><h1>这是Account组件</h1></div>', // 在这里使用定义的子组件
},
login: { // login 组件
template: "<h3>这是登录组件</h3>"
}
}
});
</script>
</body>
</html>
DOM模板解析说明
在自定义组件中使用这些受限制的元素时会导致一些问题
<table>
<my-row>...</my-row>
</table>
自定义组件被认为是无效的内容,因此在渲染的时候会导致错误。这时要使用特殊的 is 属性来挂载组件
<table>
<tr is="my-row"></tr>
</table>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<table border="1" cellpadding="5" cellspacing="0">
<my-row></my-row>
<tr is="my-row"></tr>
</table>
</div>
<script type="text/javascript">
new Vue({
el:'#app',
components:{
myRow:{
template:'<tr><td>123456</td></tr>'
}
}
});
</script>
</body>
</html>
组件选项
Vue 的组件最核心的选项有以下几个:
模板(template)
初始数据(data)
接受的外部参数(props)
方法(methods)
生命周期钩子函数(lifecycle hooks)
组件接受的选项大部分与 Vue 实例一样,相同的部分就不再赘述了。我们重点说一下 二者不同的选项 data 和 props,data 在 5.2.1 中已经讲解过了,所以本小节主要讲解 props, 它用于接收父组件传递的参数。
组件 props
组件中更重要的是组件间进行通信,选项props是组件中非常重要的一个选项,起到父子组件间桥梁的作用。
静态props
子组件使用父组件的数据
<div id="app">
<my-componet message="来至父组件的数据!!"></my-componet>
</div>
<script type="text/javascript">
Vue.component('my-componet', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 可以用在模板内
// 同样也可以在 vm 实例中像 “this.message” 这样使用
template: '<span>{{ message }}</span>'
})
new Vue({
el:'#app'
});
</script>
动态 props
动态接收父组件数据到子组件
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="parentMessage">
<my-componet :message="parentMessage"></my-componet>
</div>
<script type="text/javascript">
Vue.component('my-componet', {
props: ['message'],
template: '<span>{{ message }}</span>'
})
new Vue({
el:'#app',
data:{
parentMessage:''
}
});
</script>
</body>
</html>
这里使用 v-model 绑定了父组件数据 parentMessage,当在输入框中输入数据时,子组件接收到的 props“’message’”也会实时响应,并更新组件模板。
对于初学者常犯的一个错误,如果你在父组件中直接传递数字、布尔值、数组、对象时, 它所传递默认值字符串。如果想传递一个实际的数值,需要使用 v-bind ,从而让它的值被当作 JavaScript 表达式计算
<div id="app">
<my-componet message="1+1"></my-componet><br>
<my-componet :message="1+1"></my-componet>
</div>
<script type="text/javascript">
Vue.component('my-componet', {
props: ['message'],
template: '<span>{{ message }}</span>'
})
new Vue({
el:'#app'
});
</script>
props 验证
当组件给其他人使用时,推荐进行数据验证。
验证的type类型可以是:String、Number、Boolean、Function、Object、Array等
如果传入子组件的 message 不是数字,则抛出警告
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<parent></parent>
</div>
<script>
var childNode = {
template: '<div>{{message}}</div>',
props:{
'message':Number
}
}
var parentNode = {
template:'<div class="parent"><child :message="msg"></child></div>',
components: {
'child': childNode
},
data(){
return{
msg: '123'
}
}
};
new Vue({ // 创建根实例
el: '#example',
components: {
'parent': parentNode
}
})
</script></body></html>
单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。之所以这样设计,是尽可能将父子组件解耦,避免子组件无意中修改了父组件的状态
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example">
<parent></parent>
</div>
<script>
var childNode = {
template:
'<div class="child"><div><span>子组件数据</span>' +
'<input v-model="childMsg"> </div> <p>{{childMsg}}</p></div>' ,
props:['childMsg']
}
var parentNode = {
template:
'<div class="parent"><div><span>父组件数据</span>' +
'<input v-model="msg"></div><p>{{msg}}</p> <child :child-msg="msg"></child></div>',
components: {
'child': childNode
},
data(){
return {
'msg':'match'
}
}
};
new Vue({// 创建根实例
el: '#example',
components: {
'parent': parentNode
}
})
</script>
</body>
</html>
运行结果:父组件数据变化时,子组件数据会相应变化;而子组件数据变化时,父组件数据不变,并在控制台显示警告
组件通信
组件关系有下面三种:父—>子、子—>父、非父子。
自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<my-component v-on:myclick="onClick"></my-component>
</div>
<script>
Vue.component('my-component', {
template:'<div>' +
'<button type="button" @click="childClick">点击我触发自定义事件</button></div>' ,
methods: {
childClick () {
this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
}
}
})
new Vue({
el: '#app',
methods: {
onClick () {
console.log(arguments)
}
}
})
</script>
</body>
</html>
on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
基础用法
标签
//部分代码省略
<div class="" id="app">
<my-component>
<p>hi,slots</p>
</my-component>
</div>
<script>
Vue.component('my-component', {
template:'<div><slot></slot><div>'
});
new Vue({
el: "#app"
});
</script>
尽管内容分发这个概念看起来极为复杂,而实际上可以简单了解为把HTML标签传入组件的一种方法。所以归根结底,内容分发是一种为组件传递参数的方法。
编译作用域
组件作用域简单地说是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child-component v-show="someChildProperty"></child-component>
</div>
<script>
Vue.component('child-component', {
template: '<div>这是子组件内容</div>',
data: function () {
return {
someChildProperty: true
}
}
})
new Vue({
el:'#app'
})
</script>
</body>
</html>
这里someChildProperty绑定的是父组件的数据,所以是无效的,获取不到数据。如果想在子组件上绑定,可以是如下代码
<div id="app">
<child-component ></child-component>
</div>
<script>
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">这是子组件内容</div>',
data: function () {
return {
someChildProperty: true
}
}
})
new Vue({
el:'#app'
})
</script>
默认 slot
如果要父组件在子组件中插入内容 ,必须要在子组件中声明slot 标签 ,如果子组件模板不包含插口,父组件的内容将会被丢弃
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 1.2 那组件innerHTML位置以后不管有任何代码,都会被放进插槽那个坑里面去 -->
<index>
<span>首页</span>
<span>首页</span>
<span>首页</span>
<h1>手机</h1>
</index>
</div>
<script>
// 插槽的作用就是组件外部取代码片段放到组件内部来
// 定义默认插槽通过slot组件定义,定义好了之后,就相当于一个坑,你可以把它理解为电脑上usb插口
Vue.component('index', {
template:' <div>index</div>'
})
var vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
页面显示结果为:index。所有子组件中的内容都不会被显示,被丢弃。
要想父组件在 子组件中插入内容,必须要在子组件中声明 slot 标签,
<script>
vue. component ( 'index', {
template : '<div><slot></slot>index </div>'
})
var vm=new vue ( {
el : '#app ' ,
})
</script>
具名 slot
slot 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 标签可以有不同的名字。
使用方法。
父组件要在分发的标签中添加属性”slot=name 名”。
子组件在对应分发位置上的 slot 标签添加属性”name=name 名”
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child>
<span slot="one">123456</span>
<span slot="two">abcdef</span>
</child>
</div>
<script>
new Vue({
el:'#app',
components:{
child:{
template:"<div><slot name='two'></slot>我是子组件<slot name='one'></slot></div>"
}
}
});
</script>
</body>
</html>
作用域插槽
作用域插槽更具代表性的应用是列表组件,允许组件自定义应该如何渲染列表每一项
<div id="app">
<child></child>
</div>
<script>
Vue.component('child', {
data(){
return {
list:[1,2,3,4]
}
},
template: '<div> <ul>' +
'<li v-for="item of list">{{item}}</li></ul></div>',
})
var vm = new Vue({
el: '#app'
})
</script>
如果需要child组件在很多地方会被调用,我希望在不同的地方调用child的组件时,这个列表到底怎么循环,列表的样式不是child组件控制的,而是外部child模版占位符告诉我们组件的每一项该如何渲染,也就是说这里不用li标签,而是要用slot标签
<div id="app">
<child>
<template slot-scope="props"> <!--固定写法,属性值可以自定义-->
<li>{{props.item}}</li> <!--用插值表达式就可以直接使用-->
</template>
</child>
</div>
<script>
Vue.component('child', {
data(){
return {
list:[1,2,3,4]
}
},
template: '<div> <ul>' +
'<slot v-for="item of list" :item=item></slot> </ul></div>',
})
var vm = new Vue({
el: '#app'
})
</script>
动态组件
让多个组件使用同一个挂载点,并动态切换,这就是动态组件。通过使用保留的
<!---省略部分代码-->
<div id="app">
<button @click="change">切换页面</button>
<component :is="currentView"></component>
</div>
<script>
new Vue({
el: '#app',
data:{
index:0,
arr:[
{template:'<div>我是主页</div>'},
{template:'<div>我是提交页</div>'},
{template:'<div>我是存档页</div>'}
],
},
computed:{
currentView(){
return this.arr[this.index];
}
},
methods:{
change(){
this.index = (++this.index)%3;
}
}
})
</script>
</body>
</html>
component 标签中 is 属性决定了当前采用的子组件,:is 是 v-bind 的简写,绑定了父组 件中 data 的 currentView 属性。点击按钮时,会更改数组 arr 的索引值,同时也修改了子组 件的内容。
keep-alive
<!--省略部分代码-->
<div id="app">
<button @click="change">切换页面</button>
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</div>
<script>
new Vue({
el: '#app',
data:{
index:0,
arr:[
{template:'<div>我是主页</div>'},
{template:'<div>我是提交页</div>'},
{template:'<div>我是存档页</div>'}
],
},
computed:{
currentView(){
return this.arr[this.index];
}
},
methods:{
change(){
/*es6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。*/
let len = this.arr.length;
this.index = (++this.index)% len;
}
}
})
</script>
</body>
</html>
activated 钩子函数
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click='toShow'>点击显示子组件</button>
<!----或者<component v-bind:is="which_to_show" keep-alive></component>也行----->
<keep-alive>
<component v-bind:is="which_to_show" ></component>
</keep-alive>
</div>
<script>
// 创建根实例
var vm = new Vue({
el: '#app',
data: {
which_to_show: "first"
},
methods: {
toShow: function () { //切换组件显示
var arr = ["first", "second", "third", ""];
var index = arr.indexOf(this.which_to_show);
if (index < 2) {
this.which_to_show = arr[index + 1];
} else {
this.which_to_show = arr[0];
}
console.log(this.$children);
}
},
components: {
first: { //第一个子组件
template: "<div>这里是子组件1</div>"
},
second: { //第二个子组件
template: "<div>这里是子组件2,这里是延迟后的内容:{{hello}}</div>",
data: function () {
return {
hello: ""
}
},
activated: function (done) { //执行这个参数时,才会切换组件
console.log('beixi')
var self = this;
var startTime = new Date().getTime(); // get the current time
//两秒后执行
while (new Date().getTime() < startTime + 2000){
self.hello='我是延迟后的内容';
}
}
},
third: { //第三个子组件
template: "<div>这里是子组件3</div>"
}
}
});
</script>
</body>
</html>
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从 服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个 工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
<div id="app">
<async-example></async-example>
</div>
<script>
Vue.component ( 'async-example ', function (resolve, reject){
setTimeout (function (){
//向resolve 回调传递组件定义
resolve ( {
template: '<div>这是异步渲染的内容!</div>'
})
},1000)
})
new vue ( {
el : '#app'
})
</script>
如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到 组件定义的时候被调用。你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是 为了演示异步,如何获取组件取决于你自己。比如把组件配置成一个对象配置,通过 Ajax 来请求,然后调用 reslove 传入配置选项。
5.6.5 ref 和$refs(了解即可)
在 Vue 中一般很少会用到直接操作 DOM,但不可避免有时候需要用到,这时我们可以 通过 ref 和$refs 来实现:
ref: ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件 的 $refs 对象上,如果是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,如果是 在子组件上,引用就指向组件的实例。
refs 是一个对象,持有已注册过 ref 的所有的子组件。
普通获取 DOM 的方式
普通获取 DOM 的方式
<div id="app">
<input type="button" value="获取h3的值"@click="getElement"><h3 id="myh3">我是h3</ h3>
</div>
<script>
var vm=new vue ( {
el : "#app" ,
data : { } ,
methods : {
getElement ( ) {
//通过getElementById方式获取DOM对象
console.log (document. getElementById ( "myh3" ) .innerHTML) ;
}
}
})
</script>
ref 使用
<div id="app">
<input type="button" value="获取h3的值"@click="getElement">
<h3 id="myh3" ref="myh3">我是h3</h3>
</div>
3.ref在组件中使用
在子组件中使用 ref属性,会将子组件添加到父组件的$refs对象中,
<div id="app">
<input type="button" value="获取h3的值" @click="getElement">
<h3 id="myh3" ref="myh3">我是h3</h3>
<hr>
<login ref="mylogin"></login>
</div>