自定义事件
通过prop属性,父组件可以向子组件传递数据,而子组件的自定义事件就是用来将内部的数据报告给父组件的。
<div id="app3">
<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: '#app3',
methods: {
onClick () {
console.log(arguments)
}
}
})
</script
点击按钮 控制台打印出如下
如上所示,共分为以下步骤:
子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去
this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
第一个参数是自定义事件的名字
后面的参数是依次想要发送出去的数据
父组件利用v-on为事件绑定处理器
<my-component2 v-on:myclick="onClick"></my-component2>
这样,在Vue实例的methods方法中就可以调用传进来的参数了
注意: 在使用v-on绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:myclick="onClick",不然,子组件暴露出来的数据就无法获取到了
绑定原生事件
如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on
<my-component v-on:click.native="doTheThing"></my-component>
探究v-model
v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。比如input控件。不使用v-model,可以这样实现数据的双向绑定:
<div id="app4">
<input type="text" v-bind:value="text" v-on:input="changeValue($event.target.value)">
{{text}}
</div>
<script>
new Vue({
el: '#app4',
data: {
text: '444'
},
methods: {
changeValue (value) {
this.text = value
}
}
})
</script>
上面的代码同样实现了数据的双向绑定。其本质就是:
- 把input的value特性绑定到Vue实例的属性text上,text改变,input中的内容也会改变
- 然后把表单的input事件处理函数设置为Vue实例的一个方法,这个方法会根据输入参数改变Vue中text`的值
- 相应的,在input中输入内容时,触发了input事件,把event.target.value传给这个方法,最后就实现了改变绑定的数据的效果。
而v-model就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。
使用自定义事件创建自定义的表单输入组件
理解了v-model的内幕,也就可以把这个效果用在自定义表单组件上了。
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。model 选项可以用来避免这样的冲突:
<div id="app5">
<base-checkbox v-model="lovingVue"></base-checkbox>
<div>{{lovingVue}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `<input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)"> `
})
var vm = new Vue({
el: "#app5",
data: {
lovingVue: false
}
})
</script>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
注意 你仍然需要在组件的 props 选项里声明 checked 这个 prop。
动态组件
通过使用保留的
<div id="app6">
<select v-model="currentComponent">
<option value="home">home</option>
<option value="posts">post</option>
<option value="archive">about</option>
</select>
<component :is="currentComponent"></component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
new Vue({
el: '#app6',
data: {
currentComponent: 'home'
},
components: {
home: {
template: `<header>这是home组件</header>`
},
posts: {
template: `<header>这是posts组件</header>`
},
archive: {
template: `<header>这是archive组件</header>`
}
}
})
</script>
保留切换出去的组件,避免重新渲染
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component :is="currentComponent">
</component>
</keep-alive>
插槽
单个slot
上面用到的很多组件的使用方式是这样的:
<component></component>
也就是说组件中是空的,没有放置任何文本或元素。但是原生的html元素都是可以进行嵌套的,div里面放table
什么的。自定义组件开闭标签之间也可以放置内容,不过需要在定义组件时使用slot。
slot相当于子组件设置了一个地方,如果在调用它的时候,往它的开闭标签之间放了东西,那么它就把这些东西放到slot中。
- 当子组件中没有slot时,父组件放在子组件标签内的东西将被丢弃;
- 子组件的slot标签内可以放置内容,当父组件没有放置内容在子组件标签内时,slot中的内容会渲染出来;
- 当父组件在子组件标签内放置了内容时,slot中的内容被丢弃
列子
<div id="app"> //父组件模板:
<h1>我是父组件的标题</h1>
<my-component>
<p>这是一些初始内容</p>
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
new Vue({
el: "#app",
data: {},
components: {
'my-component': {
template: `
<div>//子组件的模板
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时才会显示。
</slot>
</div> `
}
}
})
</script>
渲染 结果:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
</div>
</div>
具名slot
slot可以有很多个。那么子组件对于父组件放置的多余的内容如何放到各个slot中呢?方法就是子组件给每个slot起一个名字name,父组件放置多余的元素时,给每个元素的slot属性分配一个代表slot的名字。到时候,多余的内容就会根据自己的slot属性去找具有对应名字的slot元素。
注意:
- 子组件可以有一个匿名的slot,当分发的多余内容找不到对应的slot时,就会进入这里面
- 如果子组件没有匿名的slot,当分发的多余内容找不到对应的slot时,就会被丢弃、
例如,假定我们有一个 app-layout 组件,它的模板为:
<div id="app">
<app-layout>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</app-layout>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
new Vue({
el: "#app",
data: {},
components: {
'my-component': {
template: `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div> `
}
}
})
</script>
渲染结果:
<div class="container">
<header>
<h1>这里可能是一个页面标题</h1>
</header>
<main>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
</main>
<footer>
<p>这里有一些联系信息</p>
</footer>
</div>
作用域插槽
作用域插槽也是一个插槽slot,但是他可以把数据传递给到父组件的特定元素内,然后有父组件决定如何渲染这些数据。
1.首先,子组件的slot需要有一些特性(prop)
Vue.component('my-component4', {
template: `<div>
<slot :text="hello" message="world"></slot>
</div>`,
data () {
return {
hello: [1,'2']
}
}
})
2.父组件在调用子组件时,需要在里面添加一个template元素,并且这个template元素具有scope特性
<div id="app7">
<my-component4>
<template scope="props">
</template>
</my-component4>
</div>
scope特性的值,就代表了所有子组件传过来的数据组成的对象。相当于
props = {
text: '',
message: ''
}
3.最后,父组件就可以在template中渲染子组件传过来的数据了
<div id="app7">
<my-component4>
<template slot-scope="props">
<span>{{props.text}}</span>
<span>{{props.message}}</span>
</template>
</my-component4>
</div>
4.Vue支持将作用域插槽的属性解构。所以上述代码可以简写为:
<div id="app7">
<my-component4>
<template slot-scope="{text, message}">
<span>{{text}}</span>
<span>{{message}}</span>
</template>
</my-component4>
</div>
作用域插槽也是插槽,只不过是多加了些特性,然后父组件多进行了些处理。