什么是组件?
组件(component)是Vue.js最强大的功能之一,核心目标是扩展HTML元素,封装可重用的代码。我们可以把组件代码按照template、style、script的拆分方式,放置到对应的 .vue 文件中。
Vue.js的组件可以理解为预先定义好行为的ViewModel类,一个组件可以预定义很多选项,但最核心的是以下几个:
模板(template) -- 模板声明了数据和最终展现给用户的DOM之间的映射关系
初始数据(data) -- 一个组件的初始数据状态。对于可复用的组件来说,通常是私有的状态
接受的外部参数(props) -- 组件之间通过参数来进行数据的传递和共享。参数默认是单向绑定(由上而下),但也可以显示声明为双向绑定
方法(methods) -- 对数据的改动操作一般都在组件的方法内进行,可以通过v-on指令将用户输入事件和组件方法进行绑定
生命周期钩子函数(lifecycle hooks) -- 一个组件会触发多个生命周期钩子函数,比如created、attached、destroyed等。在这些钩子函数中,我们可以封装一些自定义的逻辑。和传统的MVC相比,这可以理解为Controller的逻辑分散到了这些钩子函数中
组件注册
1、全局注册 Vue.component(tagName, options)
Vue.component('my-component', { // 选项 })
如上所示,第一个参数是注册组件的名称(即在HTML中我们可以这样使用组件:<didi-component></didi-component>);第二个参数是组件的构造函数,它可以是Function,也可以是Object
Function -- 第二个参数可以是用Vue.extend()创建的一个组件构造器
var myComponent = Vue.extend({ //选项... })
Object -- 第二个参数传入选项对象,Vue.js在背后自动调用Vue.extend()
//在一个步骤中扩展与注册 Vue.component('didi-component',{ template:'<div>A custom component!</div>' })
组件在注册之后,便可以在父实例的模块中以自定义元素 <my-component></my-component> 的形式使用。要确保在初始化根实例 之前 注册了组件:
全局注册案例一:
<div id="example">
<my-component></my-component>
</div>
<script src="//cdn.bootcss.com/vue/2.0.7/vue.js"></script>
<script>
//注册
Vue.component('my-component',{
template:'<div>A custom component!</div>'
})
//创建根实例
new Vue({
el:'#example'
})
</script>
全局注册案例二:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<aaa></aaa>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
//注册全局组件
Vue.component('aaa',{
template:'<table><thead><tr><th>姓名</th><th>性别</th><th>年龄</th></tr></thead><tbody>' +
'<tr><td>李雷</td><td>男</td><td>12</td></tr>' +
'<tr><td>吉姆</td><td>男</td><td>13</td></tr>' +
'<tr><td>露西</td><td>女</td><td>11</td></tr>' +
'</tbody></table>'
});
//创建根实例
var vm = new Vue({
el:'#app'
})
</script>
</body>
</html>
全局注册案例三:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//cdn.bootcss.com/bootstrap/3.0.1/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div id="app">
<template id="my-component">
<form action="myForm.php">
<div class="form-group">
<label for="uName">姓名:</label>
<input type="text" id="uName" name="uName">
</div>
<div class="form-group">
<label for="gender">性别:</label>
<label class="radio-inline">
<input type="radio" name="gender" id="male" value="male"> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="female" value="female"> 女
</label>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="text" id="age" name="age">
</div>
<div class="form-group">
<label for="interests">兴趣爱好:</label>
<label class="checkbox-inline">
<input type="checkbox" name="interests" id="reading" value="reading"> 阅读
</label>
<label class="checkbox-inline">
<input type="checkbox" name="swimming" id="swimming" value="swimming"> 游泳
</label>
<label class="checkbox-inline">
<input type="checkbox" name="interests" id="basketball" value="basketball"> 篮球
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
//注册全局组件
Vue.component('#my-component');
//创建根实例
var vm = new Vue({
el:'#app'
})
</script>
</body>
</html>
2、局部注册
不需要每个组件都全局注册,可以让组件只能用在其它组件内。我们可以用实例选项components注册
局部注册案例一:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<aaa></aaa>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
//局部注册
var vm = new Vue({
el:'#app',
components:{
'aaa':{
template:'<h2>局部注册组件数据</h2>'
}
}
})
</script>
</body>
</html>
局部注册案例二:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<aaa></aaa>
</div>
<!--<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>-->
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
//注册一个子组件
var Child = {
template:'<h3>子组件数据</h3>'
};
//注册一个父组件
var Parent = {
//这里必须用一个容器把注册组件包起来,否则会报错
template:'<div><h1>父组件数据</h1><child></child></div>',
components:{
'child':Child
}
};
//创建一个根实例
new Vue({
el:'#app',
components:{
//<aaa>只能用在父组件模板内
'aaa':Parent
}
})
</script>
</body>
</html>
局部注册案例三:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<aaa></aaa>
</div>
<template id="bbb">
<div>
<h2>`msg`</h2>
<ul>
<li v-for="data in myData">`data`</li>
</ul>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
},
components:{
'aaa':{
template:'#bbb',
data:function(){
return{
msg:'fruits',
myData:['apple','banana','cherry','duria']
}
}
}
}
})
</script>
</body>
</html>
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 组件
因此,有必要的话请使用字符串模版。
针对is属性的小例子:
<div id="app">
<table>
<tr>
<td><label for="uName">用户名:</label></td>
<td><input type="text" id="uName"></td>
</tr>
<tr>
<td>性别:</td>
<td>
<label for="male">男</label>
<input type="radio" id="male" value="male" v-model="gender">
<label for="female">女</label>
<input type="radio" id="female" value="female" v-model="gender">
<span>Gender: `gender`</span>
</td>
</tr>
<tr>
<td>年龄:</td>
<td>
<input type="number" id="uAge" v-model="age">
<span>Age: `age`</span>
</td>
</tr>
<tr>
<td>兴趣:</td>
<td>
<input type="checkbox" id="reading" value="reading" v-model="interests">
<label for="reading">阅读</label>
<input type="checkbox" id="swimming" value="swimming" v-model="interests">
<label for="swimming">游泳</label>
<input type="checkbox" id="running" value="running" v-model="interests">
<label for="running">游泳</label>
<span>Interest: `interests`</span>
</td>
</tr>
<tr is="my-row"></tr>
<!--<tr>--> <!-- 如果是这种写法的话,页面什么内容都不显示,审查元素也不存在 -->
<!--<my-row></my-row>-->
<!--</tr>-->
<tr>
<td>所在城市:</td>
<td>
<select v-model="cities">
<option value="GZ" selected>广州</option>
<option value="BJ">北京</option>
<option value="SZ">深圳</option>
<option value="SH">上海</option>
</select>
<span>City: `cities`</span>
</td>
</tr>
<tr>
<td>自我介绍:</td>
<td>
<textarea></textarea>
</td>
</tr>
</table>
</div>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
Vue.component('my-row',{
template:'<td>报名院校:</td><td>' +
'<select>' +
'<option value="qinghua" selected>清华大学</option>' +
'<option value="beida">北京大学</option>' +
'<option value="hagongda">哈尔冰工业大学</option>' +
'<option value="fudan">复旦大学</option>' +
'</select>' +
'</td>'
})
new Vue({
el:'#app',
data:{
gender:'',
age:'',
interests:[],
cities:''
}
})
</script>
data必须是函数
使用组件时,大多数选项可以被传入到 Vue 构造器中,有一个例外: data 必须是函数。 实际上,如果你这么做:
<div id="app">
<my-component></my-component>
</div>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
Vue.component('my-component',{
template:'<span>`message`</span>',
data:{
message:'hello'
}
})
</script>
那么 Vue 会在控制台发出警告,告诉你在组件中 data 必须是一个函数。最好理解这种规则的存在意义。
<div id="app">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
var data={counter:0};
Vue.component('simple-counter',{
template:'<button @click="counter+=1">`counter`</button>',
data:function(){
return data
}
})
new Vue({
el:'#app'
})
</script>
由于这三个组件共享了同一个 data , 因此增加一个 counter 会影响所有组件!我们可以通过为每个组件返回新的 data 对象来解决这个问题:
<div id="app">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
Vue.component('simple-counter',{
template:'<button @click="counter+=1">`counter`</button>',
data:function(){
return {counter:0}
}
})
new Vue({
el:'#app'
})
</script>
现在每个 counter 都有它自己内部的状态了:
data函数必须返回一个对象
还是把上面的例子稍作修改,示例代码如下:
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
Vue.component('counter',{
template:'<button @click="count+=1">`count`</button>',
data:function(){
return count=0
}
})
new Vue({
el:'#app'
})
</script>
看上去似乎没有什么问题,但是页面并没有渲染出效果来,打开控制台可以看到报错信息,data函数必须返回一个对象
组件数据传递
组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件。然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。
在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。看看它们是怎么工作的。
Props ---> 父组件把数据传递给子组件
使用Props传递数据
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。
prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项 声明 “prop”:
Vue.component('child', { // 声明 props props: ['message'], // 就像 data 一样,prop 可以用在模板内 // 同样也可以在 vm 实例中像 “this.message” 这样使用 template: '<span>{{ message }}</span>' })
然后向它传入一个普通字符串:
<child message="hello!"></child>
先看第一种情况,不使用 props 传递数据
<div id="app">
<child message="hello"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
Vue.component('child',{
template:'<span>`message`</span>'
})
new Vue({
el:"#app"
})
</script>
查看页面,发现报错了
属性或方法“message”在实例中未定义,但是在渲染的时候被引用了,在data选项中需要确保声明该属性
我们可以这样修改:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<child message="hello"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
Vue.component('child',{
template:'<span>`message`</span>',
data(){
return {message:'你好,世界!'}
}
});
var vm = new Vue({
el:'#app'
})
</script>
</body>
</html>
第二种情况,使用 props 传递数据
<div id="app">
<child message="hello"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
Vue.component('child',{
template:'<span>`message`</span>',
//声明 props
props:['message']
});
new Vue({
el:"#app",
data:{
message:'你好'
}
})
</script>
此时,页面可以渲染出自定义的组件了
同时,注意观察上面的代码,Vue实例中有一个同名的message 属性 '你好',但是页面渲染出来的却是 hello。这就回到了props 的作用域问题了
props作用域
组件实例的作用域是孤立的,也就是说,即使组件与组件之间有同名属性,值也不共享
<div id="app">
<component1></component1>
<component2></component2>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
new Vue({
el:'#app',
components:{
'component1':{
template:'<p>`msg`</p>',
data:function(){
return {msg:'组件一'}
}
},
'component2':{
template:'<p>`msg`</p>',
data:function(){
return {msg:'组件二'}
}
}
}
})
</script>
两个组件都使用了同一个属性msg,但是渲染出来的内容却是不一样的
再来看个例子,模仿帖子评论的“点赞”和 “反对”功能:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//cdn.bootcss.com/bootstrap/3.0.1/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div id="app">
<counter heading="Like"></counter>
<counter heading="Dislike"></counter>
</div>
<template id="counter-template">
<div>
<h1>`heading`</h1>
<button @click="count+=1">`count`</button>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'counter':{
template:'#counter-template',
data:function(){
return {count:0}
}
}
}
})
</script>
</body>
</html>
如下图所示,页面提示报错,heading未定义,需要在data属性中声明。前面的例子也提到过,可以在data函数中返回一个 heading的对象,但是这里的heading 的值是动态的,可以是 Like 也可以是 Dislike,所以最好的办法就是遵循规则,把父组件 counter 要传给 子组件 template 的数据放到 props里面
添加一个 props 属性:
var vm = new Vue({ el:'#app', components:{ 'counter':{ template:'#counter-template', //把父组件 counter 传递给子组件 template 的数据放到 props属性中 props:['heading'], data:function(){ return {count:0} } } } })
正常显示:
这里,再增加一个颜色提示,点赞的按钮用绿色,反对的按钮用红色
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<!-- 这里如果用color就可以正常显示,换成其他的名字就无法显示背景色-->
<counter heading="Likes" color="green"></counter>
<counter heading="Dislikes" color="red"></counter>
</div>
<template id="counter-template">
<div>
<h1>`heading`</h1>
<button @click="count+=1" :style="{background: color}">`count`</button>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'counter':{
template:'#counter-template',
//把父组件 counter 传递给子组件 template 的数据放到 props属性中
props:['heading','color'],
data:function(){
return {count:0}
}
}
}
})
</script>
</body>
</html>
这里有两个同名属性heading和color,但是作用域不同,所以渲染出来的颜色和内容都不同。
在这个例子中,counter作为父组件,counter-template作为子组件,父组件向子组件传递数据“Likes,Dislikes,green,red”,子组件通过点击事件向父组件发送消息
驼峰命名法 VS 短横线命名法
HTML 特性不区分大小写。当使用非字符串模版时,prop的名字形式会从 camelCase 转为 kebab-case(短横线隔开):
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>' })
再次说明,如果你使用字符串模版,不用在意这些限制。看看刚才两种不同命名方法展现的页面效果
动态Props
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:
语法:<child v-bind:子组件的值="父组件的属性"></child>
看一个官方提供的示例:
<div id="app">
<input type="text" v-model="parentMsg"><br>
<child :my-message="parentMsg"></child>
</div>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
Vue.component('child',{
template:'<p>`myMessage`</p>',
props:['myMessage']
})
new Vue({
el:'#app',
data:{
parentMsg:''
}
})
</script>
再来看一个例子:
<div id="app">
<child :btn="h"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
data:{
h:'hello'
},
components:{
'child':{
props:['btn'],
template:'<button>`btn`</button>',
data:function(){
return {'btn':123} //子组件同名的属性被父组件覆盖了
}
}
}
}).$mount('#app');
</script>
按照前面的说法,每当父组件的数据变化时,子组件也会跟着变化。也就是说,这个例子中的child 子组件中的btn 属性永远被父组件中的 h 属性所覆盖
查看页面截图:
改变父组件中 h 属性的值,子组件中的内容会跟着改变
字面量语法 VS 动态语法
初学者常犯的一个错误是使用字面量语法传递数值:
<!-- 传递了一个字符串"1" -->
<comp some-prop="1"></comp>
因为它是一个字面 prop ,它的值以字符串 "1" 而不是以实际的数字传下去。如果想传递一个实际的 JavaScript 数字,需要使用 v-bind ,从而让它的值被当作 JavaScript 表达式计算:
<!-- 传递实际的数字 -->
<comp v-bind:some-prop="1"></comp>
上面的这段内容是官方文档提供的,简单理解就是:
(1)、不加 v-bind 传递的就是字符串,哪怕是数字1,也是一个字符串,而不是Number
(2)、加 v-bind 传递的就是Javascript 表达式,这样才能传递父组件中的值
(3)、加 v-bind 以后,如果能找到父组件的值,就使用父组件的值;如果找不到i,就看成是一个Javascript 表达式(例如1+2看做3,{a:1}看做是一个对象)
字面量语法示例代码(不加 v-bind):
<div id="app">
<!-- 不加 v-bind -->
<child msg="1+2"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'child':{
props:['msg'],
template:'<button>`msg`</button>'
}
}
})
</script>
页面渲染效果:
动态语法示例代码(加 v-bind):
<div id="app"> <!-- 加 v-bind --> <child :msg="1+2"></child> </div> <script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script> <script> var vm = new Vue({ el:'#app', components:{ 'child':{ props:['msg'], template:'<button>`msg`</button>' } } }) </script>
页面渲染结果:
单向数据流
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。
绑定修饰符 (Vue2.0 已经移除了 .sync 和 .once),只做了解,重点掌握单向绑定
.sync ---> 双向绑定
.once ---> 单次绑定
代码示例如下:
<!--单向绑定-->
<child :msg="parentMsg"></child>
<!--双向绑定 Vue 2.0不支持-->
<child :msg.sync="parentMsg"></child>
<!--单次绑定Vue 2.0不支持-->
<child :msg.once="parentMsg"></child>
示例一:单向绑定:
<div id="app">
父组件:
<input v-model="msg">
<hr>
子组件:
<child :message="msg"></child>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
msg:'Hello World'
},
components:{
'child':{
props:['message'],
template:'<input v-model="message">'
}
}
})
</script>
修改父组件的内容,子组件随之改变:
修改子组件的内容,控制台立即报错:大概意思是
避免直接改变一个 prop 因为父组件的值在重新渲染时会被覆盖,可以用一个基于 prop 值得 data 或 computed 属性来代替
示例二:双向绑定: (Vue2.0不支持,并且会报同样的错误)
<div id="app">
父组件:
<input v-model="msg">
<hr>
子组件:
<child :message.sync="msg"></child>
</div>
<!--注意:引用的是 Vue 1.0-->
<script src="//cdn.bootcss.com/vue/1.0.26/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
msg:'Hello World'
},
components:{
'child':{
props:['message'],
template:'<input v-model="message">'
}
}
})
</script>
改变子组件的内容,父组件同样跟着变化。再次提醒:Vue2.0不支持这个用法
上面的示例一中可以看到,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop 。如果你这么做了,Vue 会在控制台给出警告。
通常有两种改变 prop 的情况:
1、prop 作为初始值传入,子组件之后只是将它的初始值作为本地数据的初始值使用;
2、prop 作为需要被转变的原始值传入。
更确切的说这两种情况是:
1、定义一个局部 data 属性,并将 prop 的初始值作为局部数据的初始值。
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
2、定义一个 computed 属性,此属性从 prop 的值计算得出。
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
Prop验证
组件可以为 props 指定验证要求。如果未指定验证要求,Vue 会发出警告。当组件给其他人使用时这很有用。
prop 是一个对象而不是字符串数组时,它包含验证要求:
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
type 也可以是一个自定义构造器,使用 instanceof 检测。
当 prop 验证失败了, Vue 将拒绝在子组件上设置此值,如果使用的是开发版本会抛出一条警告。
总结:子组件要想获取父组件data
在调用子组件:
<bbb :m="数据"></bbb>
子组件之内:
props:['m','myMsg']
props:{
'm':String,
'myMsg':Number
}
子组件把数据发送给父组件
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎么做呢?那就是自定义事件
使用 v-on 绑定自定义事件
每个 Vue 实例都实现了 事件接口(Events interface),即:
> 使用 $on( eventName ) 监听事件
> 使用 $emit( eventName ) 触发事件
Vue的事件系统分离自浏览器的EventTarget API。尽管它们的运行类似,但是$on 和 $emit 不是addEventListener 和 dispatchEvent 的别名。
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件
示例代码一:
<div id="app">
父组件:<br>
<p>`total`</p>
子组件:<br>
<button-counter @increment="incrementTotal"></button-counter>
<button-counter @increment="incrementTotal"></button-counter>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
total:0
},
methods:{
incrementTotal:function(){
this.total+=1;
}
},
components:{
'button-counter':{
props:[], //父组件并没有往子组件传出数据,所以不需要
template:'<button @click="increment">`counter`</button>',
//data必须是一个函数并返回一个对象
data:function(){
return {counter:0}
},
methods:{
increment:function(){
this.counter+=1;
this.$emit('increment')
}
}
}
}
})
</script>
这个例子中,子组件已经完全和它外部完全解耦了。它所做的只是触发一个父组件关心的内部事件
示例代码二:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<template id="parent-template">
<div>
<span>父组件的数据是:</span> ---> `parentMsg`
<child></child>
</div>
</template>
<template id="child-template">
<div>
<span>子组件的数据是:</span> ---> `childMsg`
<button >发送</button>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var Parent = Vue.component('parent',{
template: '#parent-template',
data(){
return {parentMsg:'111'}
}
});
var Child = Vue.component('child',{
template:'#child-template',
data(){
return {childMsg:'99999'}
}
})
var vm = new Vue({
el:'#app',
components:{
'parent': Parent
}
})
</script>
</body>
</html>
这里准备了两个组件,期望的目标是点击发送按钮,把子组件的数据发送给父组件,用99999 替换 111
注意:核心两步
vm.$emit(自定义事件名, 数据) <!-- 自定义事件名不推荐驼峰命名法-->
v-on:自定义事件名= 方法
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<template id="parent-template">
<div>
<span>父组件的数据是:</span> ---> `parentMsg`
<!-- 自定义事件名如果写成驼峰命名法,就无法获取数据也不报错,不得其解-->
<child @get-message="get"></child>
</div>
</template>
<template id="child-template">
<div>
<span>子组件的数据是:</span> ---> `childMsg`
<button @click="send">发送</button>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
//注册父组件
var Parent = Vue.component('parent',{
template: '#parent-template',
data(){
return {parentMsg:'111'}
},
//接收子组件传过来的数据
methods:{
get:function(msg){
alert(msg);
this.parentMsg = msg;
}
}
});
//注册子组件
var Child = Vue.component('child',{
template:'#child-template',
data(){
return {childMsg:'99999'}
},
//把子组件数据发送给父组件
methods:{
send:function(){
this.$emit('get-message', this.childMsg)
}
}
});
var vm = new Vue({
el:'#app',
components:{
'parent': Parent
}
});
</script>
</body>
</html>
查看页面效果:
slot 分发内容
在使用组件时,常常要像这样组合它们:
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
注意两点:
1、<app> 组件不知道它的挂载点会有什么内容。挂载点的内容是由<app>的父组件决定的。
2、<app> 组件很可能有它自己的模版。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 (或 “transclusion” 如果你熟悉 Angular)。Vue.js 实现了一个内容分发 API ,参照了当前 Web组件规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。
为了避免被文字绕晕了,直接上一段代码来理解:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent>
<div>
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>橙子</li>
</ul>
</div>
</parent>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'parent':{
template:'<h1>水果,水果,在哪里?</h1>'
}
}
})
</script>
</body>
</html>
这里定义了一个组件 parent,然后又给parent 定义了一个模板 h1,最终显示的效果却并没有 ul 列表
解决的办法就是使用 内容分发,也就是前面提到的 <slot> 插槽,slot 本意就是“位置,槽”,在这里简单说就是 占个位置
1、单个 slot
父组件的内容将被抛弃,除非子组件模板包含 <slot>。如果子组件模板只有一个没有特性的 slot,父组件的整个内容将插到 slot 所在的地方并替换它。
<slot> 标签的内容视为回退内容,回退内容在子组件的作用域内编译,当宿主元素为空并且没有内容插入时显示这个回退内容。
把上面的代码稍作修改
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent>
<div>
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>橙子</li>
</ul>
</div>
</parent>
</div>
<template id="fruit">
<div>
<h1>水果,水果,在哪里?</h1>
<slot></slot>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'parent':{
template:'#fruit'
}
}
})
</script>
</body>
</html>
页面效果:
2、具名 slot
<slot> 元素可以用一个特殊特性 name 配置如何分发内容,多个 slot 可以有不同的名字,具名的 slot 将匹配内容片段中有对应 slot 特性的元素
仍然可以有一个匿名 slot,作为找不到匹配的内容片段的回退插槽,它是默认 slot。如果没有默认 slot,这些找不到匹配的内容片段将被抛弃
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent>
<div>
<ul slot="fruit-slot">
<li>苹果</li>
<li>香蕉</li>
<li>橙子</li>
</ul>
<ul slot="turnip-slot">
<li>白萝卜</li>
<li>红萝卜</li>
<li>绿萝卜</li>
</ul>
</div>
</parent>
<hr>
<parent>
</parent>
</div>
<template id="fruit">
<div>
<h1>水果,水果,在哪里?</h1>
<slot name="fruit-slot">这是默认的情况</slot>
<slot></slot>
<slot name="turnip-slot">这是默认的情况2</slot>
</div>
</template>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
components:{
'parent':{
template:'#fruit'
}
}
})
</script>
</body>
</html>
动态组件
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的<component>元素,动态地绑定到它的 is 特性
<component :is="组件名称"></component>
示例代码一:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//cdn.bootcss.com/bootstrap/3.0.1/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div id="app">
<input type="button" value="aaa组件" @click="a='aaa'" class="btn btn-success">
<input type="button" value="bbb组件" @click="a='bbb'" class="btn btn-danger">
<component :is="a"></component>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
a:'aaa'
},
components:{
'aaa':{
template:'<h2>我是 aaa 组件</h2>>'
},
'bbb':{
template:'<h2>我是 bbb 组件</h2>'
}
}
})
</script>
</body>
</html>
示例代码二:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//cdn.bootcss.com/bootstrap/3.0.1/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div id="app">
<div class="radio">
<label for="one">
<input type="radio" id="one" value="fast" v-model="currentView"> fast
</label>
</div>
<div class="radio">
<label for="two">
<input type="radio" id="two" value="bus" v-model="currentView"> bus
</label>
</div>
<div class="radio">
<label for="three">
<input type="radio" id="three" value="business" v-model="currentView"> business
</label>
</div>
<fast v-show="show"></fast>
<bus v-show="show"></bus>
<business v-show="show"></business>
<component :is="currentView">
<!--组件在 vm.currentView 变化时改变-->
</component>
</div>
<script src="//cdn.bootcss.com/vue/2.0.8/vue.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
currentView:'fast',
show:false
},
components:{
'fast':{
template:'<h2>滴滴快车</h2>'
},
'bus':{
template:'<h2>滴滴巴士</h2>'
},
'business':{
template:'<h2>滴滴专车</h2>'
}
}
})
</script>
</body>
</html>
通过is 属性绑定的 vm.currentView 变量值,控制展示的组件,效果如下所示:
keep-alive
如果把切换出去的组件保留在内存中,则可以保留它的状态或避免重新渲染。为此,可以添加一个 keep-alive 指令参数。
<component :is="currentView" keep-alive>
<!--组件在 vm.currentView 变化时改变-->
</component>
Vue.js 为其组件设计了一个 [keep-alive] 的特性,如果这个特性存在,那么在组件被重复创建时,会通过缓存机制快速创建组件,以提升视图更新的性能。
转载于:https://blog.51cto.com/dapengtalk/1874605