Vue.js实战第七章笔记(二)
7.3 组件通信
组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信。
7.3.1 自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件。我们在介绍指令v-on时有提到,v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。
子组件用on()来监听子组件的事件。
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件,例如:
<div id="app">
<p>总数:{{total}}</p>
<my-compoment @increase="handleGetTotal"
@reduce="handleGetTotal"></my-compoment>
</div>
<script>
Vue.component('my-component',{
props: ['width'],
template: '\
<div>\
<button @click="handleIncrease">+1</button>\
<button @click="handleReduce">-1</button>\
</div>',
data: function () {
return {
counter: 0
}
},
methods: {
handleIncrease: function(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce: function () {
this.counter--;
this.$emit('reduce',this.counter);
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleGetTotal: function (total) {
this.total = total;
}
}
})
</script>
上面的示例中,子组件有两个按钮,分别实现加1和减1的效果,在改变组件的data "counter"后,通过emit()方法的第一个参数是自定义时间的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或填写多个。
除了用v-on在组件上监听自定义事件外,也可以监听DOM事件,这时可以用.native修饰符表示监听的是一个原生事件,监听的是该组件的根元素,代码示例如下:
<my-compoment v-on:click.native="handleClick"></my-compoment>
7.3.2 使用v-model
Vue 2.x可以在自定义组件上使用v-model指令,例如:
<div id="app">
<p>总数:{{total}}</p>
<my-compoment v-model="total"></my-compoment>
</div>
<script>
Vue.component('my-component',{
template: '<button @click="handleClick">+1</button>',
data: function () {
return {
counter: 0
}
},
methods: {
handleClick: function () {
this.counter++;
this.$emit('input', this.counter);
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
},
})
</script>
仍然是点击按钮加1的效果,不过这次组件$emit()的事件名是特殊的input,在使用组件的父级,并没有在my-component上使用@input=“handler”,而是直接用了v-model绑定一个数据total,这也可以称作是一个语法糖。
v-model还可以用来创建自定义的表单输入组件,进行数据双向绑定,例如:
<div id="app">
<p>总数:{{total}}</p>
<my-compoment v-model="total"></my-compoment>
<button @click="handleReduce">-1</button>
</div>
<script>
Vue.component('my-component',{
props: ['value'],
template: '<input :value="value" @input="updateValue">',
methods: {
updateValue: function (event) {
this.$emit('input', evnet.target.value);
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleReduce: function () {
this.total--;
}
}
})
</script>
实现这样一个具有双向绑定的v-model组件要满足两个要求:
(1)接收一个value属性;(2)在有新的value时触发input事件。
7.3.3 非父子组件通信
在实际业务中,除了父子组件通信外,还有很多非父子组件通信的场景,非父子组件一般有两种,兄弟组件和跨多级组件。
在Vue.js 2.x中,推荐使用一个空的Vue实例作为*事件总线(bus),也就是一个中介,例如:
<div id="app">
{{message}}
<compoment-a v-model="total"></compoment-a>
</div>
<script>
var bus = new Vue;
Vue.component('component-a',{
template: '<button @click="handleEvent">传递事件</button>',
methods: {
handleEvent: function () {
bus.$emit('on-message', '来自组件component-a的内容');
}
}
})
var app = new Vue({
el: '#app',
data: {
message: ''
},
mounted: function() {
var _this = this;
//在实例初始化时,监听来自bus实例的事件
bus.$on('on-message',function (msg) {
_this.message = msg;
});
}
})
</script>
首先创建一个名为bus的空Vue实例,里面没有任何内容;然后全局定义了组件component-a;最后创建Vue实例app,在app初始化时,也就是 在生命周期mounted钩子函数里监听了来自bus的事件on-message,而在组件component-a中,点击按钮会通过bus把事件on-message发出去,此时app就会接收到来自bus的事件,进而在回调里完成自己的业务逻辑。
除了*事件总线bus外,还有两种方法可以实现组件间通信:父链和子组件索引。
父链
在子组件中,使用this.children访问它所有的子组件,而且可以递归向上或向下无限访问,直到根实例或最内层的组件。例如:
<div id="app">
{{message}}
<compoment-a></compoment-a>
</div>
<script>
Vue.component('component-a',{
template: '<button @click="handleEvent">通过父链直接修改数据</button>',
methods: {
handleEvent: function (event) {
//访问到父链后,可以做任何操作,比如直接修改数据
this.$parent.message = '来自组件component-a的内容';
}
}
});
var app = new Vue({
el: '#app',
data: {
message: ''
}
})
</script>
尽管Vue允许这样操作,但在业务中,子组件应该尽可能地避免依赖父组件的数据,更不应该去主动修改它的数据,因为这样是的父子组件紧耦合,只看父组件,很难理解父组件的状态,因为它可能被任意组件修改,理想情况下,只看组件自己能修改它的状态。父子组件最好还是通过props和$emit()来通信。
子组件索引
当子组件较多时,通过this.$children来一一遍历出我们需要的一个组件实例是比较困难的,尤其是组件动态渲染时,它们的序列是不固定的。Vue提供了子组件索引的方法,用特殊的属性ref来为子组件指定一个索引名称,例如:
<div id="app">
<button @click="handleRef">通过ref获取子组件实例</button>
<compoment-a ref="comA"></compoment-a>
</div>
<script>
Vue.component('component-a',{
template: '<div>子组件</div>',
data: function () {
return {
message: '子组件内容'
}
}
});
var app = new Vue({
el: '#app',
methods: {
handleRef: function () {
//通过$refs来访问指定的实例
var msg = this.$refs.comA.message;
console.log(msg);
}
}
})
</script>
在父组件模板中,子组件标签上使用ref指定一个名称,并在父组件内通过this.$refs来访问指定名称的子组件。
refs。
7.4使用slot分发内容
7.4.1 什么是slot
当需要让组件组合使用,混合父组件的内容和子组件的模板时,就会用到slot,这个过程叫作内容分发(transclusion)。以<app>
为例,它有两个特点:
(1)<app>
组件不知道他的挂载点会有什么内容。挂载点的内容是由<app>
的父组件决定的。
(2)<app>
组件很可能有它自己的模板。
props传递数据、event触发事件和slot内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成的。
7.4.2 作用域
正式介绍slot前,需要先知道一个概念:编译的作用域。比如父组件中有如下模板:
<child-component>
{{message}}
</child-component>在这里插入代码片
这里的message就是一个slot,但是它绑定的是父组件的数据,而不是组件<child-component>
的数据。
7.4.3 slot的用法
单个slot
在子组件内使用特殊的<slot>
元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将代替子组件的<slot>
标签及它的内容。例如:
<div id="app">
<child-component>
<p>分发的内容</p>
<p>更多分发的内容</p>
</child-component>
</div>
<script>
Vue.component('child-component',{
template: '\
<div>\
<slot>\
<p>如果父组件没有插入内容,我将作为默认出现</p>\
</slot>\
</div>'
});
var app = new Vue({
el: '#app',
})
</script>
子组件child-component的模板内定义了一个<slot>
元素,并且用一个<p>
作为默认的内容,在父组件没有使用slot时,会渲染这段默认的文本;如果写入了slot,那就会替换整个<slot>
。所以上例渲染后的结果为:
<div id="app">
<div>
<p>分发的内容</p>
<p>更多分发的内容</p>
</div>
</div>
注意,子组件内的备用内容,它的作用域是子组件本身。
具名slot
给<slot>
元素指定一个name后可以分发多个内容,具名slot可以与单个slot共存,例如:
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
template: '\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>'
});
var app = new Vue({
el: '#app',
})
</script>
子组件内声明了3个<slot>
元素,其中在<div class="main"
>内的<slot>
没有使用name特性,它将作为默认的slot出现,父组件没有使用slot特性的元素与内容都将出现在这里。
如果没有指定默认的匿名slot,父组件内多余的内容片段都将被抛弃。
上例最终渲染后的结果为:
<div id="app">
<div class="container">
<div class="header">
<h2>标题</h2>
</div>
<div class="main">
<p>正文内容</p>
<p>更多的正文内容</p>
</div>
<div slot="footer">
<div>底部信息</div>
</div>
</child-component>
</div>
7.4.4 作用域插槽
作用域插槽是一种特殊的slot,使用一个可以复用的模板替换已渲染的元素。例如:
<div id="app">
<child-component>
<template scope="props">
<p>来自父组件的内容</p>
<p>{{props.msg}}</p>
</template>
</child-component>
</div>
<script>
Vue.component('child-component',{
template: '\
<div class="container">\
<slot msg="来自子组件的内容"></slot>\
</div>'
});
var app = new Vue({
el: '#app',
})
</script>
观察子组件的模板,在<slot>
元素上有个类似props传递数据给组件的写法msg=“xxx”,将数据传到插槽。父组件中使用了<template>
元素,而且有一个scope="props"的特性,这里的props只是一个临时变量,就像v-for="item in items"里的item一样。template内可以通过临时变量props访问来自子组件插槽的数据msg。
将上面的示例渲染后的最终结果为:
<div id="app">
<div>
<div class="container">
<p>来自父组件的内容</p>
<p>来自子组件的内容</p>
</div>
</div>
</div>
7.4.5 访问slot
Vue.js 2.x提供了用来访问被slot分发的内容的方法$slots,例如:
<div id="app">
<child-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
template: '\
<div class="container">\
<div class="header">\
<slot name="header"></slot>\
</div>\
<div class="main">\
<slot></slot>\
</div>\
<div class="footer">\
<slot name="footer"></slot>\
</div>\
</div>',
mounted: function () {
var header = this.$slots.header;
var main = this.$slots.default;
var footer = this.$slots.footer;
console.log(footer);
console.log(footer[0].elm.innerHTML);
}
});
var app = new Vue({
el: '#app',
})
</script>
通过$ slots可以访问某个具名solt,this.$slots.default包括了所有没有被包含在具名solt中的节点。
下一篇: Java to Kotlin 语法清单