本篇资料来于官方文档:
http://cn.vuejs.org/guide/components.html
本文是在官方文档的基础上,更加细致的说明,代码更多更全。
简单来说,更适合新手阅读
(二十五)组件的定义
①组件的作用:
【1】扩展HTML元素,封装可重用的代码;
【2】组件是自定义元素,Vuejs的编译器可以为其添加特殊的功能;
【3】某些情况下,组件可以是原生HTML元素的形式,以is的方式扩展。
②写一个标准的组件:
分为以下几步:
【1】挂载组件的地方,需要是Vue实例所渲染的html元素,具体来说,比如上面的<div id=”app”></div>这样的html元素及他的子节点;
【2】定义一个组件,用
var 变量名 = Vue.extend({template:”这里是html的模板内容”})
这样的形式创建,例如:
//定义一个组件
var btn = Vue.extend({
template: "<button>这是一个按钮</button>"
})
【3】将定义的组件注册到Vue实例上,这会让指定标签,被组件的内容所替代。
如代码:
//注册他到Vue实例上
Vue.component("add-button", btn);
具体而言,每一个以下这样的标签(在Vue的根实例范围内的)
<add-button></add-button>
会被
<button>这是一个按钮</button>
所替代。
【4】以上方法是全局注册(每个Vue实例的add-button标签都会被我们定义的所替代);
解决办法是局部注册。
如代码:(这是是设置了template属性,也可以在没有这个属性的时候,在<div id=”app”></div>标签内放置<add-button></add-button>标签
<div id="app">
</div>
<script>
//定义一个组件
var btn = Vue.extend({
template: "<button>这是一个按钮</button>"
})
Vue.component("add-button", btn);
//创建根实例,也就是说让Vue对这个根生效
var vm = new Vue({
el: '#app',
template: "<add-button></add-button>"
});
</script>
③局部注册组件:
简单来说,只对这一个Vue实例生效,具体做法是,在注册那一步,跳过;
然后在声明Vue实例的时候,将添加到components这个属性中(他是一个对象,以KV形式放置)(注意,这个单词多一个s)
如代码:
<div id="app">
</div>
<script>
//定义一个组件
var btn = Vue.extend({
template: "<button>这是一个按钮</button>"
})
//创建根实例,也就是说让Vue对这个根生效
var vm = new Vue({
el: '#app',
template: "<add-button></add-button>",
components: {
"add-button": btn
}
});
</script>
注:
根据官方教程,这种方法(指局部注册),也适用于其他资源,比如指令、过滤器和过渡。
④步骤简化:
【1】定义组件和注册组件结合起来一步完成:
//定义一个组件
Vue.component("add-button", {
template: "<button>这是一个按钮</button>"
});
【2】局部注册时,定义和注册一步完成:
//创建根实例,也就是说让Vue对这个根生效
var vm = new Vue({
el: '#app',
template: "<add-button></add-button>",
components: {
"add-button": {
template: "<button>这是一个按钮</button>"
}
}
});
⑤data属性
直接给组件添加data属性是不可以的(无效);
原因在于,假如这么干,那么组件的data属性有可能是一个对象,而这个对象也有可能是外部传入的(例如先声明一个对象,然后这个对象作为data的值),可能导致这个组件的所有副本,都共享一个对象(那个外部传入的),这显然是不对的。
因此,data属性应该是一个函数,然后有一个返回值,这个返回值作为data属性的值。
且这个返回值应该是一个全新的对象(即深度复制的,避免多个组件共享一个对象);
如代码:
var vm = new Vue({
el: '#app',
template: "<add-button></add-button>",
components: {
"add-button": {
template: "<button>这是一个按钮{{btn}}</button>",
data: function () {
return {btn: "123"};
}
}
}
});
另外,假如这样的话,btn的值是一样的(因为他们实际上还是共享了一个对象)
<div id="app">
</div>
<div id="app2">
</div>
<script>
var obj = {btn: "123"};
var vm = new Vue({
el: '#app',
template: "<add-button></add-button>",
components: {
"add-button": {
template: "<button>这是一个按钮{{btn}}</button>",
data: function () {
return obj;
}
}
}
});
obj.btn = "456";
var vm2 = new Vue({
el: '#app2',
template: "<add-button></add-button>",
components: {
"add-button": {
template: "<button>这是一个按钮{{btn}}</button>",
data: function () {
return obj;
}
}
}
});
</script>
注1:
el属性用在Vue.extend()中时,也须是一个函数。
注2:
给子组件添加methods无需使用函数返回值,直接如正常那样添加即可。
示例代码:
<div id="app">
子组件:
<test></test>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
components: {
test: {
props: ['test'],
template: "<input @keyup='findParent' v-model='test'/>",
methods: {
findParent: function () {
console.log("happened");
}
}
}
}
});
</script>
⑥is特性:
【1】按照官方教程,一些HTML元素对什么元素可以放在它之中是有限制的;
简单来说,如果我要在table标签内复用某个组件,这个组件展开后是tr标签,但是展开前不是,那么就无法正常运行(被放置在table标签内);
如代码(错误写法,会渲染错误):
<div id="app">
<table>
<tr>
<td>索引</td>
<td>ID</td>
<td>说明</td>
</tr>
<thetr v-for="i in items" v-bind:id="i" :index="$index"></thetr>
</table>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4]
},
methods: {
toknowchildren: function () { //切换组件显示
console.log(this.$children);
}
},
components: {
thetr: { //第一个子组件
template: "<tr>" +
"<td>{{index}}</td>" +
"<td>{{id}}</td>" +
"<td>这里是子组件</td>" +
"</tr>",
props: ['id', 'index']
}
}
});
</script>
渲染结果如下:
<div id="app">
<tr><td>0</td><td>1</td><td>这里是子组件</td></tr>
<tr><td>1</td><td>2</td><td>这里是子组件</td></tr>
<tr><td>2</td><td>3</td><td>这里是子组件</td></tr>
<tr><td>3</td><td>4</td><td>这里是子组件</td></tr>
<table>
<tbody>
<tr>
<td>索引</td>
<td>ID</td>
<td>说明</td>
</tr>
</tbody>
</table>
</div>
可以明显发现,内容没有被放在table之中。
正确写法如下:
<div id="app">
<button @click="toknowchildren">点击让子组件显示</button>
<table>
<tr>
<td>索引</td>
<td>ID</td>
<td>说明</td>
</tr>
<tr is="thetr" v-for="i in items" v-bind:id="i" :index="$index"></tr>
</table>
</div>
【2】更多冲突参照URL
http://cn.vuejs.org/guide/components.html#u6A21_u677F_u89E3_u6790
(二十六)props数据传递
①组件实例的作用域:
是孤立的,简单的来说,组件和组件之间,即使有同名属性,值也不共享。
<div id="app">
<add></add>
<del></del>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
"add": {
template: "<button>btn:{{btn}}</button>",
data: function () {
return {btn: "123"};
}
},
del: {
template: "<button>btn:{{btn}}</button>",
data: function () {
return {btn: "456"};
}
}
}
});
</script>
渲染结果是:
2个按钮,第一个的值是123,第二个的值是456(虽然他们都是btn)
②使用props绑定静态数据:
【1】这种方法用于传递字符串,且值是写在父组件自定义元素上的。
【2】下面示例中的写法,不能传递父组件data属性中的值
【3】会覆盖模板的data属性中,同名的值。
示例代码:
<div id="app">
<add btn="h"></add>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
h: "hello"
},
components: {
"add": {
props: ['btn'],
template: "<button>btn:{{btn}}</button>",
data: function () {
return {btn: "123"};
}
}
}
});
</script>
这种写法下,btn的值是h,而不是123,或者是hello。
【4】驼峰写法
假如插值是驼峰式的,
而在html标签中,由于html的特性是不区分大小写(比如LI和li是一样的),因此,html标签中要传递的值要写成短横线式的(如btn-test),以区分大小写。
而在props的数组中,应该和插值保持一致,写成驼峰式的(如btnTest)。
例如:
props: ['btnTest'],
template: "<button>btn:{{btnTest}}</button>",
正确的写法:
<add btn-test="h"></add>
假如插值写短横线式,或者是html标签写成驼峰式,都不能正常生效。(除非插值不写成驼峰式——跳过大小写的限制,才可以)
③利用props绑定动态数据:
简单来说,就是让子组件的某个插值,和父组件的数据保持一致。
标准写法是(利用v-bind):
<add v-bind:子组件的值="父组件的属性"></add>
如代码:
<div id="app">
<add v-bind:btn="h"></add>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
h: "hello"
},
components: {
"add": {
props: ['btn'],
template: "<button>btn:{{btn}}</button>",
data: function () {
return {'btn': "123"}; //子组件同名的值被覆盖了
}
}
}
});
</script>
说明:
【1】btn使用的父组件data中 h的值;
【2】子组件的data的函数中返回值被覆盖了。
【3】也就是说,使用v-bind的是使用父组件的值(根据属性名),没有使用v-bind的是将标签里的数值当做字符串来使用。
【4】依然需要使用props,否则他会取用自己data里的btn的值
④字面量和动态语法:
【1】简单来说,不加v-bind的,传递的是字面量,即当做字符串(例如1也是字符串,而不是number类型);
【2】加上v-bind的,传递的是JS表达式(因此才能传递父组件的值);
【3】加上v-bind后,如果能找到父组件的值,那么使用父组件的值;如果没有对应的,则将其看做一个js表达式(例如1+2看做3,{a:1}看做是一个对象);
如代码:
<div id="app">
<add v-bind:btn="1+2"></add>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
h: "hello"
},
components: {
"add": {
props: ['btn'],
template: "<button>btn:{{btn}}</button>"
}
}
});
</script>
这里的btn的值是3(而不是没有加v-bind时,作为字符串的1+2)
⑤props的绑定类型:
【1】简单来说,分为两种类型,即单向绑定(父组件能影响子组件,但相反不行)和双向绑定(子组件也能影响父组件);
【2】单向绑定示例:(默认,或使用.once)
<div id="app">
父组件:
<input v-model="val"><br/>
子组件:
<test v-bind:test-Val="val"></test>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
components: {
"test": {
props: ['testVal'],
template: "<input v-model='testVal'/>"
}
}
});
</script>
说明:
当父组件的值被更改后,子组件的值也随之更改;
当子组件的值被更改后,父组件的值不会变化,而假如再次修改父组件的值,子组件会再次同步。
另外需要注意的是,子组件如果要同步绑定,那么子组件的input需要是v-model,而不能是value属性(那样只能单项绑定,且修改子组件的值后会失去绑定)
【3】双向绑定:
需要使用“.sync”作为修饰词
如示例:
<div id="app">
父组件:
<input v-model="val"><br/>
子组件:
<test :test.sync="val"></test>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
components: {
"test": {
props: ['test'],
template: "<input v-model='test'/>"
}
}
});
</script>
效果是无论你改哪一个的值,另外一个都会随之变动。
【4】props验证:
简单来说,当组件获取数据时,进行验证,只有符合条件的时候,才会使用之。
写法是将props变为一个对象,被验证是值是对象的key,验证条件是和key对应的value。
例如:
props: {
test: {
twoWay: true
}
},
验证test这个变量是不是双向绑定,如果不是,则报错。(注意,这个不能用于验证单向绑定)。
示例代码如下:
<div id="app">
父组件:
<input v-model="val"><br/>
子组件:
<test :test="val"></test>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
components:{
test:{
props: {
test: {
twoWay: true
}
},
template: "<input v-model='test'/>"
}
}
});
</script>
更多验证查看官方教程:
http://cn.vuejs.org/guide/components.html#Prop__u9A8C_u8BC1
(二十七)父子组件通信
①访问子组件、父组件、根组件;
this.$parent 访问父组件
this.$children 访问子组件(是一个数组)
this.$root 根实例的后代访问根实例
示例代码:
<div id="app">
父组件:
<input v-model="val"><br/>
子组件:
<test :test="val"></test>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
components: {
test: {
props: ['test'],
template: "<input @keyup='findParent' v-model='test'/>",
methods: {
findParent: function () {
console.log(this.$parent); //访问根组件
console.log(this.$parent.val); //访问根组件的val属性
console.log(this.$parent.$children.indexOf(this)); //查看当前能否在其父组件的子组件中找到索引
console.log(this.$parent === this.$root); //查看父组件和根组件是不是全等的(因为他的父组件就是根组件)
}
}
}
}
});
</script>
当在子组件的输入框按键弹起时,显示内容依次为:
父组件、父组件的输入框的值(默认情况是1)、0(表示是父组件的children属性中的第一个元素)、true(由于父组件就是根组件,所以是全等的);
通过这样的方法,可以在组件树中进行互动。
②自定义事件:
首先,事件需要放置在events属性之中,而不是放置在methods属性中(新手很容易犯的错误),只能触发events属性中的事件,而methods属性中的事件是无法触发的。
事件 |
说明 |
$on(事件名) |
事件名的类型是字符串(下同),调用它可以通过this.$on()来调用; |
$emit(事件名, 参数) |
用于触发事件,参数是用于传递给事件的参数。这个用于触发同级事件(当前组件的) |
$dispatch(事件名, 参数) |
①向上派发事件,用于向父组件传播。 ②会首先触发当前组件的同名事件(如果有); ③然后会向上冒泡,当遇到第一个符合的父组件的事件后触发并停止; ④当父组件的事件的返回值设为true会继续冒泡去找下一个。 |
$broadcast(事件名, 参数) |
①向下广播事件,用于向所有子组件传播。 ②默认情况是仅限子组件; ③子组件事件的返回值是true,才会继续向该子组件的孙组件派发; ④不会触发自身同名事件; |
其次,向上派发和向下广播有所区别:向上派发会触发自身同名事件,而向下广播不会;
第三,向上派发和向下广播默认只会触发直系(子或者父,不包括祖先和孙)的事件,除非事件返回值为true,才会继续在这一条线上继续。
第四,事件不能显式的通过 this.事件名 来调用它。
示例代码:
<div id="app">
父组件:
<button @click="parentClick">点击向下传播broadcast</button>
<br/>
子组件1:
<children1></children1>
<br/>
另一个子组件1:
<another-children1></another-children1>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
methods: {
parentClick: function () {
this.$broadcast("parentClick", "abc");
}
},
events: {
childrenClick: function () {
console.log("childrenClick-Parent");
},
parentClick: function () {
console.log("parentClick-Parent");
return true;
}
},
components: {
children1: { //这个无返回值,不会继续派发
template: "<button>children1</button></br>子组件2:<children2></children2>",
events: {
childrenClick: function () {
console.log("childrenClick-children1");
},
parentClick: function (msg) {
console.log("parentClick-Children1");
console.log("message:" + msg);
}
},
components: {
children2: {
template: "<button @click='findParent'>children-Click</button>",
methods: {
findParent: function () {
this.$dispatch('childrenClick');
}
},
events: {
childrenClick: function () {
console.log("childrenClick-children2");
},
parentClick: function (msg) {
console.log("parentClick-Children2");
console.log("message:" + msg);
}
}
}
}
},
anotherChildren1: { //这个是返回值为true,会继续向子组件的子组件派发
template: "<button>anotherChildren1</button></br>另一个子组件2:<another-children2></another-children2>",
events: {
childrenClick: function () {
console.log("childrenClick-anotherChildren1");
return true;
},
parentClick: function (msg) {
console.log("parentClick-anotherChildren1");
console.log("message:" + msg);
return true;
}
},
components: {
anotherChildren2: {
template: "<button @click='findParent'>anotherChildren2-Click</button>",
methods: {
findParent: function () {
this.$dispatch('childrenClick');
}
},
events: {
childrenClick: function () {
console.log("childrenClick-anotherChildren2");
},
parentClick: function (msg) {
console.log("parentClick-anotherChildren2");
console.log("message:" + msg);
}
}
}
}
}
}
});
</script>
},
parentClick: function () {
console.log("parentClick-anotherChildren2");
}
}
}
}
}
}
});
</script>
说明:
【1】点击父组件的按钮,会向下广播,然后触发子组件1本身,另外一个子组件1,以及另一个子组件2;
【2】点击子组件2的按钮,会触发子组件2的事件和子组件1的事件,但不会触发父组件的按钮;
【3】点击另一个子组件2的按钮,会触发另一个子组件2的事件,另一个子组件1的事件和父组件的事件(因为另一个子组件1的事件的返回值为true);
③使用v-on绑定自定义事件:
【1】简单来说,子组件触发某个事件(events里的方法)时,父组件也会执行某个方法(父组件methods里的方法)。
【2】触发的绑定写在模板之中(即被替换的那个template模板中),可以多个子组件的事件绑定一个父组件的方法,或者不同子组件的事情绑定不同父组件的方法,但是不能同一个子组件事件绑定多个父组件的方法。
【3】子组件派发消息传递的参数,即使子组件的事件没有参数,也不影响将参数传递给父组件的方法(即父组件的方法可以接受到子组件方法获取的参数)
如示例:
<div id="app">
父组件:
<button>点击向下传播broadcast</button>
<br/>
子组件1:
<!--绑定写在这里,可以多个绑定同一个,或者不同绑定不同的,但不能一个绑定多个-->
<children v-on:test="parent" @test2="another"></children>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
val: 1
},
methods: {
parent: function (arg) {
console.log(arg);
console.log("the first method with test event");
},
another: function () {
console.log("another method");
}
},
components: {
children: { //这个无返回值,不会继续派发
template: "<button @click='childClick'>children1</button></br><button @click='childClick2'>children1</button>",
methods: {
childClick: function () {
this.$emit("test", 'the argument for dispatch');
},
childClick2: function () {
this.$emit("test2");
}
},
events: {
test: function () {
console.log("test");
},
test2: function () {
console.log("test2");
}
}
}
}
});
</script>
④子组件索引
简单来说:就是可以直接从索引获取到子组件,然后就可以调用各个子组件的方法了。
添加索引方法是:在标签里添加v-ref:索引名
调用组件方法是:vm.$ref.索引名
也可以直接在父组件中使用this.$ref.索引名
这个时候,就可以获得组件了,然后通过组件可以调用他的方法,或者是使用其数据。
示例代码:
<div id="app">
父组件:
<button @click="todo">触发子组件的事件</button>
<br/>
子组件1:
<!--绑定写在这里,可以多个绑定同一个,或者不同绑定不同的,但不能一个绑定多个-->
<children v-ref:child></children>
</div>
<script>
var vm = new Vue({
el: '#app',
methods: {
todo: function () {
this.$refs.child.fromParent(); //通过索引调用子组件的fromParent方法
}
},
components: {
children: { //这个无返回值,不会继续派发
template: "<button>children1</button>",
methods: {
fromParent: function () {
console.log("happened fromParent by ref");
}
}
}
}
});
</script>
(二十八)Slot分发内容
①概述:
简单来说,假如父组件需要在子组件内放一些DOM,那么这些DOM是显示、不显示、在哪个地方显示、如何显示,就是slot分发负责的活。
②默认情况下
父组件在子组件内套的内容,是不显示的。
例如代码:
<div id="app">
<children>
<span>12345</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
children: { //这个无返回值,不会继续派发
template: "<button>为了明确作用范围,所以使用button标签</button>"
}
}
});
</script>
显示内容是一个button按钮,不包含span标签里面的内容;
③单个slot
简单来说,只使用这个标签的话,可以将父组件放在子组件的内容,放到想让他显示的地方。
<div id="app">
<children>
<span>12345</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
children: { //这个无返回值,不会继续派发
template: "<button><slot></slot>为了明确作用范围,所以使用button标签</button>"
}
}
});
</script>
例如这样写的话,结果是:
<button><span>12345</span>为了明确作用范围,所以使用button标签</button>
即父组件放在子组件里的内容,插到了子组件的<slot></slot>位置;
注意,即使有多个标签,会一起被插入,相当于用父组件放在子组件里的标签,替换了<slot></slot>这个标签。
④具名slot
将放在子组件里的不同html标签放在不同的位置
父组件在要分发的标签里添加 slot=”name名” 属性
子组件在对应分发的位置的slot标签里,添加name=”name名” 属性,
然后就会将对应的标签放在对应的位置了。
示例代码:
<div id="app">
<children>
<span slot="first">12345</span>
<span slot="second">56789</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
children: { //这个无返回值,不会继续派发
template: "<button><slot name='first'></slot>为了明确作用范围,<slot name='second'></slot>所以使用button标签</button>"
}
}
});
</script>
显示结果为:(为了方便查看,已手动调整换行)
<button>
<span slot="first">12345</span>
为了明确作用范围,
<span slot="second">56789</span>
所以使用button标签
</button>
⑤分发内容的作用域:
被分发的内容的作用域,根据其所在模板决定,例如,以上标签,其在父组件的模板中(虽然其被子组件的children标签所包括,但由于他不在子组件的template属性中,因此不属于子组件),则受父组件所控制。
示例代码:
<div id="app">
<children>
<span slot="first" @click="tobeknow">12345</span>
<span slot="second">56789</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
methods: {
tobeknow: function () {
console.log("It is the parent's method");
}
},
components: {
children: { //这个无返回值,不会继续派发
template: "<button><slot name='first'></slot>为了明确作用范围,<slot name='second'></slot>所以使用button标签</button>"
}
}
});
</script>
当点击文字12345的区域时(而不是按钮全部),会触发父组件的tobeknow方法。
但是点击其他区域时则没有影响。
官方教程是这么说的:
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译
⑥当没有分发内容时的提示:
假如父组件没有在子组件中放置有标签,或者是父组件在子组件中放置标签,但有slot属性,而子组件中没有该slot属性的标签。
那么,子组件的slot标签,将不会起到任何作用。
除非,该slot标签内有内容,那么在无分发内容的时候,会显示该slot标签内的内容。
如示例代码:
<div id="app">
<children>
<span slot="first">【12345】</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
components: {
children: { //这个无返回值,不会继续派发
template: "<div><slot name='first'><button>【如果没有内容则显示我1】</button></slot>为了明确作用范围,<slot name='last'><button>【如果没有内容则显示我2】</button></slot>所以使用button标签</div>"
}
}
});
</script>
说明:
【1】name=’first’的slot标签被父组件对应的标签所替换(slot标签内部的内容被舍弃);
【2】name=’last’的slot标签,因为没有对应的内容,则显示该slot标签内部的内容。
⑦假如想控制子组件根标签的属性
【1】首先,由于模板标签是属于父组件的,因此,将子组件的指令绑定在模板标签里,是不可以的(因为他归属于父组件);
【2】假如需要通过父组件控制子组件是否显示(例如v-if或者v-show),那么这个指令显然是属于父组件的(例如放在父组件的data下面)。可以将标签写在子组件的模板上。
如代码:
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<children v-if="abc">
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
abc: false
},
methods: {
toshow: function () {
this.abc = !this.abc;
}
},
components: {
children: { //这个无返回值,不会继续派发
template: "<div>这里是子组件</div>"
}
}
});
</script>
说明:
通过父组件(点击按钮,切换v-if指令的值)控制子组件是否显示。
【3】假如需要通过子组件,控制子组件是否显示(比如让他隐藏),那么这个指令显然是属于子组件的(会将值放在子组件的data属性下),那么就不能像上面这么写,而是必须放置在子组件的根标签中。
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<children>
<span slot="first">【12345】</span>
<!--上面这行不会显示-->
</children>
</div>
<script>
var vm = new Vue({
el: '#app',
methods: {
toshow: function () {
this.$children[0].tohidden = true;
}
},
components: {
children: { //这个无返回值,不会继续派发
template: "<div v-if='tohidden' @click='tohide'>这里是子组件</div>",
data: function () {
return {
tohidden: true
}
},
methods: {
tohide: function () {
this.tohidden = !this.tohidden;
}
}
}
}
});
</script>
说明:
点击子组件会让子组件消失;
点击父组件的按钮,通过更改子组件的tohidden属性,让子组件重新显示。
子组件的指令绑定在子组件的模板之中(如此才能调用);
(二十九)组件——动态组件
①简单来说,就是几个组件放在一个挂载点下,然后根据父组件的某个变量来决定显示哪个,或者都不显示。
②动态切换:
在挂载点使用component标签,然后使用v-bind:is=”组件名”,会自动去找匹配的组件名,如果没有,则不显示;
改变挂载的组件,只需要修改is指令的值即可。
如示例代码:
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<component v-bind:is="which_to_show"></component>
</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 < 3) {
this.which_to_show = arr[index + 1];
} else {
this.which_to_show = arr[0];
}
}
},
components: {
first: { //第一个子组件
template: "<div>这里是子组件1</div>"
},
second: { //第二个子组件
template: "<div>这里是子组件2</div>"
},
third: { //第三个子组件
template: "<div>这里是子组件3</div>"
},
}
});
</script>
说明:
点击父组件的按钮,会自动切换显示某一个子组件(根据which_to_show这个变量的值来决定)。
③keep-alive
简单来说,被切换掉(非当前显示)的组件,是直接被移除了。
在父组件中查看this.$children属性,可以发现,当子组件存在时,该属性的length为1,而子组件不存在时,该属性的length是0(无法获取到子组件);
假如需要子组件在切换后,依然需要他保留在内存中,避免下次出现的时候重新渲染。那么就应该在component标签中添加keep-alive属性。
如代码:
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<component v-bind:is="which_to_show" keep-alive></component>
</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 < 3) {
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</div>"
},
third: { //第三个子组件
template: "<div>这里是子组件3</div>"
},
}
});
</script>
说明:
初始情况下,vm.$children属性中只有一个元素(first组件),
点击按钮切换后,vm.$children属性中有两个元素,
再次切换后,则有三个元素(三个子组件都保留在内存中)。
之后无论如何切换,将一直保持有三个元素。
④activate钩子
简单来说,他是延迟加载。
例如,在发起ajax请求时,会需要等待一些时间,假如我们需要在ajax请求完成后,再进行加载,那么就需要用到activate钩子了。
具体用法来说,activate是和template、data等属性平级的一个属性,形式是一个函数,函数里默认有一个参数,而这个参数是一个函数,执行这个函数时,才会切换组件。
为了证明他的延迟加载性,在服务器端我设置当发起某个ajax请求时,会延迟2秒才返回内容,因此,第一次切换组件2时,需要等待2秒才会成功切换:
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<component v-bind:is="which_to_show"></component>
</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 < 3) {
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,这里是ajax后的内容:{{hello}}</div>",
data: function () {
return {
hello: ""
}
},
activate: function (done) { //执行这个参数时,才会切换组件
var self = this;
$.get("/test", function (data) { //这个ajax我手动在服务器端设置延迟为2000ms,因此需要等待2秒后才会切换
self.hello = data;
done(); //ajax执行成功,切换组件
})
}
},
third: { //第三个子组件
template: "<div>这里是子组件3</div>"
}
}
});
</script>
代码效果:
【1】第一次切换到组件2时,需要等待2秒后才能显示(因为发起ajax);
【2】在有keep-alive的情况下,第二次或之后切换到组件2时,无需等待;但ajax内容,需要在第一次发起ajax两秒后才会显示;
【3】在无keep-alive的情况下(切换掉后没有保存在内存中),第二次切换到组件2时,依然需要等待。
【4】等待时,不影响再次切换(即等待组件2的时候,再次点击切换,可以直接切换到组件3);
说明:
【1】只有在第一次渲染组件时,才会执行activate,且该函数只会执行一次(在第一次组件出现的时候延迟组件出现)
【2】没有keep-alive时,每次切换组件出现都是重新渲染(因为之前隐藏时执行了destroy过程),因此会执行activate方法。
⑤transition-mode过渡模式
简单来说,动态组件切换时,让其出现动画效果。(还记不记得在过渡那一节的说明,过渡适用于动态组件)
默认是进入和退出一起完成;(可能造成进入的内容出现在退出内容的下方,这个下方指y轴方面偏下的,等退出完毕后,进入的才会出现在正确的位置);
transition-mode=”out-in”时,动画是先出后进;
transition-mode=”in-out”时,动画是先进后出(同默认情况容易出现的问题);
示例代码:(使用自定义过渡名和animate.css文件)
<div id="app">
<button @click="toshow">点击让子组件显示</button>
<component v-bind:is="which_to_show" class="animated" transition="bounce" transition-mode="out-in"></component>
</div>
<script>
Vue.transition("bounce", {
enterClass: 'bounceInLeft',
leaveClass: 'bounceOutRight'
})
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 < 3) {
this.which_to_show = arr[index + 1];
} else {
this.which_to_show = arr[0];
}
}
},
components: {
first: { //第一个子组件
template: "<div>这里是子组件1</div>"
},
second: { //第二个子组件
template: "<div>这里是子组件2,这里是ajax后的内容:{{hello}}</div>",
data: function () {
return {
hello: ""
}
}
},
third: { //第三个子组件
template: "<div>这里是子组件3</div>"
}
}
});
</script>
(三十)组件——杂项
①组件和v-for
简单来说,就是组件被多次复用;
例如表格里的某一行,又例如电商的商品橱窗展示(单个橱窗),都可以成为可以被复用的组件;
只要编写其中一个作为组件,然后使数据来源成为一个数组(或对象,但个人觉得最好是数组),通过v-for的遍历,组件的每个实例,都可以获取这个数组中的一项,从而生成全部的组件。
而数据传输,由于复用,所以需要使用props,将遍历结果i,和props绑定的数据绑定起来,绑定方法同普通的形式,在模板中绑定。
示例代码:
<div id="app">
<button @click="toknowchildren">点击让子组件显示</button>
<table>
<tr>
<td>索引</td>
<td>ID</td>
<td>说明</td>
</tr>
<tr is="the-tr" v-for="i in items" v-bind:id="i" :index="$index"></tr>
</table>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4]
},
methods: {
toknowchildren: function () { //切换组件显示
console.log(this.$children);
}
},
components: {
theTr: { //第一个子组件
template: "<tr>" +
"<td>{{index}}</td>" +
"<td>{{id}}</td>" +
"<td>这里是子组件</td>" +
"</tr>",
props: ['id','index']
}
}
});
</script>
说明:
【1】记得将要传递的数据放在props里!
【2】将index和索引$index绑定起来,因为索引从0开始,因此索引所在列是从0开始;id是和遍历items的i绑定在一起的,因此id从1开始。
【3】可以在父组件中,通过this.$children来获取子组件(但是比较麻烦,特别是组件多的时候,比较难定位);
②编写可复用的组件:
简单来说,一次性组件(只用在这里,不会被复用的)跟其他组件紧密耦合是可以的,但是,可复用的组件应当定义一个清晰的公开接口。(不然别人怎么用?)
可复用的组件,基本都是要和外部交互的,而一个组件和外部公开的交互接口有:
【1】props:允许外部环境数据传递给组件;
【2】事件:允许组件触发外部环境的action,就是说通过在挂载点添加v-on指令,让子组件的events触发时,同时触发父组件的methods;
【3】slot:分发,允许将父组件的内容插入到子组件的视图结构中。
如代码:
<div id="app">
<p>这是第一个父组件</p>
<widget
:the-value="test"
@some="todo">
<span>【第一个父组件插入的内容】</span>
</widget>
</div>
<div id="app2">
<p>这是第二个父组件</p>
<widget @some="todo">
</widget>
</div>
<script>
Vue.component("widget", {
template: "<button @click='dosomething'><slot></slot>这是一个复用的组件,点击他{{theValue}}</button>",
methods: {
dosomething: function () {
this.$emit("some");
}
},
events: {
some: function () {
console.log("widget click");
}
},
props: ['theValue']
})
var vm = new Vue({
el: '#app',
data: {
test: "test"
},
methods: {
todo: function () {
console.log("这是第一个父组件")
}
}
});
var vm_other = new Vue({
el: '#app2',
data: {
name: "first"
},
methods: {
todo: function () {
console.log("这是另外一个父组件")
}
}
});
</script>
说明:
【1】在第一个父组件中使用了分发slot,使用了props来传递值(将test的值传到子组件的theValue之中);
【2】在两个组件中,子组件在点击后,调用methods里的dosomething方法,然后执行了events里的some事件。又通过挂载点的@some=”todo”,将子组件的some事件和父组件的todo方法绑定在一起。
因此,点击子组件后,最终会执行父组件的todo方法。
【3】更改父组件中,被传递到子组件的值,会同步更改子组件的值(即二者会数据绑定);
③异步组件:
按照我的理解,简单来说,一个大型应用,他有多个组件,但有些组件无需立即加载,因此被分拆成多个组件(比如说需要立即加载的,不需要立即加载的);
需要立即加载的,显然放在同一个文件中比较好(或者同一批一起请求);
而不需要立即加载的,可以放在其他文件中,但需要的时候,再ajax向服务器请求;
这些后续请求的呢,就是异步组件;
做到这种异步功能的,就是Vue.js的功能——允许将组件定义为一个工厂函数,动态解析组件的定义。
可以配合webpack使用。
至于如何具体使用,我还不太明白,教程中写的不清,先搁置等需要的时候来研究。
链接:
http://cn.vuejs.org/guide/components.html#u5F02_u6B65_u7EC4_u4EF6
④资源命名的约定:
简单来说,html标签(比如div和DIV是一样的)和特性(比如要写成v-on这样的指令而不是vOn)是不区分大小写的。
而资源名往往是写成驼峰式(比如camelCase驼峰式),或者单词首字母都大写的形式(比如PascalCase,我不知道该怎么称呼这个,不过这样写很少的说)。
Vue.component("myTemplate", {
//......略
})
Vue.js可以自动识别这个并转换,
<my-template></my-template>
以上那个模板可以自动替换这个标签。
⑤递归组件:
简单来说,递归组件就是组件在自己里内嵌自己的模板。
组件想要递归,需要name属性,而Vue.component自带name属性。
大概样子是这样的,
<div id="app">
<my-template></my-template>
</div>
<script>
Vue.component("myTemplate", {
template: "<p><my-template></my-template></p>"
})
这种是无限递归,肯定是不行的。因此,需要控制他递归的层数,例如通过数据来控制递归,当数据为空时,则停止递归。
示例代码如下:
<ul id="app">
<li>
{{b}}
</li>
<my-template v-if="a" :a="a.a" :b="a.b"></my-template>
</ul>
<script>
Vue.component("myTemplate", {
template: '<ul><li>{{b}}</li><my-template v-if="a" :a="a.a" :b="a.b"></my-template></ul>',
props: ["a", "b"]
})
var data = {
a: {
a: {
a: 0,
b: 3
},
b: 2
},
b: 1
}
var vm = new Vue({
el: '#app',
data: data,
methods: {
todo: function () {
this.test += "!";
console.log(this.test);
}
}
});
</script>
说明:
【1】向下传递时,通过props传递a的值和b的值,其中a的值作为递归后组件的a和b的值的数据来源;
然后判断传递到递归后的组件的a的值是否存在,如果存在则继续递归;
如果a的值不存在,则停止递归。
⑥片断实例:
简单来说,所谓片断实例,就是组件的模板不是处于一个根节点之下:
片断实例代码:
Vue.component("myTemplate", {
template: '<div>1</div>' +
'<div>2</div>',
})
非片断实例:
Vue.component("myTemplate", {
template: '<div>' +
'<div>1</div>' +
'<div>2</div>' +
'</div>',
})
片断实例的以下特性被忽略:
【1】组件元素上的非流程控制指令(例如写在挂载点上的,由父组件控制的v-show指令之类,但注意,v-if属于流程控制指令);
【2】非props特性(注意,props不会被忽略,另外props是写在挂载点上的);
【3】过渡(就是transition这个属性,将被忽略);
更多的参照官方文档:
http://cn.vuejs.org/guide/components.html#u7247_u65AD_u5B9E_u4F8B
⑦内联模板
参照:http://cn.vuejs.org/guide/components.html#u5185_u8054_u6A21_u677F
反正我试了下失败了,google也没搜到相关的内容,┑( ̄Д  ̄)┍
————————组件的基本知识到这里结束————————————