目录
一、关于vue应用于组件
1、vue应用的数据配置选项
vue应用中有许多方法和配置项供开发者使用。
el:选项需要提供应用挂载到哪个根节点进行数据驱动。
data:选项需要提供应用所需的全局数据。
props:选项用于接收父组件传递的数据。
computed选项用于配置组件的计算属性。
methods选项用来配置组件中需要使用到的方法。
watch选项用于对组件属性的变化监听。
2、定义组件
应用实例可以使用component方法来定义组件,定义好组件后,可以直接在HTML文档中使用。
<div class="app">
<alert></alert>
</div>
<script>
const componentAlert = {
template: `<div><button @click="alert">点击按钮</button></div>`,
data() {
return {
count: 0,
msg: '警告信息!'
}
},
methods: {
alert() {
alert(this.msg + this.count++)
}
}
}
const app = new Vue({
el: '.app',
components: {
'alert': componentAlert
}
})
</script>
结果:
注意:上面的alert组件是挂载到app实例上的,只能在app实例上使用,如果其他页面实例需要使用,则需要注册为全局组件。
二、组件中的数据与事件传递
组件具有复用性,因此要使得组件能够在不同的应用中得到最大程度的复用与最少的内部改动,就需要组件具有一定的灵活度,即可配置性。
1、为组件添加外部属性
如果需要在使用组件时灵活地设置其按钮显示的标题,就需要使用组件中的props配置。
<div class="app">
<btn title="按钮1" bcg="red"></btn>
</div>
<div class="root">
<btn title="按钮2" bcg="green"></btn>
</div>
<script>
const component = {
template: `<div><button :style="{backgroundColor:bcg}" @click='click'>{{title}}</button></div>`,
props: ['title', 'bcg'],
methods: {
click() {
console.log('单击事件!');
}
},
}
const app = new Vue({
el: '.app',
components: {
'btn': component
}
})
const root = new Vue({
el: '.root',
components: {
'btn': component
}
})
</script>
结果:
2、处理组件事件
如果需要在使用组件时灵活地设置其逻辑,就需要使用组件中的$emit方法来传递事件。
<div class="app">
<my-click title="按钮1" @my-click="Func"></my-click>
</div>
<script>
const component = {
props: ['title'],
template: `<div><button @click="$emit('my-click')">{{title}}</button></div>`
}
const app = new Vue({
el: '.app',
components: {
"my-click": component
},
methods: {
Func() {
console.log('自定义组件!');
}
},
})
</script>
结果:
$emit方法在传递事件时也可以传递一些参数,很多自定义组件都有状态,我们可以将状态作为参数传递
<div class="app">
<my-click title="按钮1" @my-click="Func"></my-click>
</div>
<script>
const component = {
props: ['title'],
template: `<div><button @click="$emit('my-click',title)">{{title}}</button></div>`
}
const app = new Vue({
el: '.app',
components: {
"my-click": component
},
methods: {
Func(param) {
console.log('自定义组件!', param);
}
},
})
结果:
在传递事件之前,子组件还有一些内部的逻辑需要处理,也可以在子组件中包装一个方法,在方法内调用$emit进行事件传递
<div class="app">
<my-click title="按钮1" @my-click="click1"></my-click>
<my-click title="按钮2" @my-click="click2"></my-click>
</div>
<script>
const component = {
template: `<div><button @click="click">{{title}}</button></div>`,
props: ['title'],
methods: {
click() {
console.log('我是组件内部逻辑!');
this.$emit('my-click', this.title);
}
}
}
const app = new Vue({
el: '.app',
components: {
'my-click': component
},
methods: {
click1(param) {
console.log('我是外部逻辑1!' + param);
},
click2(param) {
console.log('我是外部逻辑2!' + param);
}
},
})
</script>
结果:
3、在组件上使用v-model指令
<div class="app">
<input type="text" v-model="inputValue">
<div class="text">{{inputValue}}</div>
<button @click="inputValue=''">清空</button>
</div>
<script>
const app = new Vue({
el: '.app',
data() {
return {
inputValue: ''
}
}
})
</script>
如果不使用v-model,要实现相同的效果
<div class="app">
<input type="text" :value="inputValue" @input="input">
<div class="text">{{inputValue}}</div>
<button @click="inputValue=''">清空</button>
</div>
<script>
const app = new Vue({
el: '.app',
data() {
return {
inputValue: ''
}
},
methods: {
input(event) {
this.inputValue = event.target.value
}
},
})
</script>
代码中先使用v-bind指令来控制输入框的内容,当属性inputValue改变后,v-bind指令会将其同步到更新输入框中,之后使用v-on:input指令来监听输入框的输入事件,当输入框的输入内容变化时,手动通过action函数来更新inputValue属性,这样就实现了双向绑定的效果。这就是v-model的基本实现原理。
为自定义组件增加v-model支持就非常简单了
<div class="app">
<my-input v-model="iptValue"></my-input>
</div>
<script>
const component = {
template: `<div><span>输入框:<input type="text" :value="inputValue" @input="handler"></span></div>`,
props: ['inputValue'],
methods: {
handler(event) {
this.$emit('input', event.target.value)
}
},
}
const app = new Vue({
el: '.app',
components: {
"my-input": component
},
data() {
return {
iptValue: ''
}
},
})
</script>
而自定义组件中的v-model却有一个缺点,就是不能双向绑定radio和checkbox元素。
<div class="app">
<my-input v-model="checked"></my-input>
</div>
<script>
const component = {
template: `<div>
<input type="checkbox" :checked="checkedValue" @change="handle">
</div>`,
props: ['checkedValue'],
methods: {
handle(event) {
this.$emit('change', event.target.checked);
}
},
}
const app = new Vue({
el: '.app',
components: {
"my-input": component
},
data() {
return {
checked: true
}
}
})
</script>
无效!
三、自定义组件的插槽
插槽是指HTML起始标签与结束标签中间的部分,通常在使用div标签时,其内部插槽的位置既可以放置要显示的文案,又可以嵌套放置其他标签。
1、组件插槽的基本使用
<div class="app">
<my-slot>
123
</my-slot>
</div>
<script>
const slotComponent = {
template: `<div style="border: 1px solid pink;width:200px;height:200px">
<slot></slot>
</div>`,
}
const app = new Vue({
el: '.app',
components: {
"my-slot": slotComponent
}
})
</script>
结果:
2、多具名插槽的用法
具名插槽是指为插槽设置一个具体名称,在使用组件时,可以通过插槽的名称来设置插槽的内容。由于具名插槽可以非常明确地指定插槽内容的位置,因此当一个组件要支持多个插槽时,通常需要使用具名插槽。
<div class="app">
<my-slot>
<template v-slot:header>
hello
</template>
<template v-slot:main>
world
</template>
<template v-slot:footer>
莎士比亚
</template>
</my-slot>
</div>
<script>
const vSlotComponent = {
template: `
<div style="border: 1px solid pink;width:200px;height:200px">
<slot name="header"></slot><hr>
<slot name="main"></slot><hr>
<slot name="footer"></slot>
</div>
`
}
const app = new Vue({
el: '.app',
components: {
"my-slot": vSlotComponent
}
})
</script>
结果:
3、动态组件的简单应用
<div class="app">
<input type="radio" value="page1" v-model="page">页面1
<input type="radio" value="page2" v-model="page">页面2
<component :is="page"></component>
</div>
<script>
const page1 = {
template: `<div style="color:'red'">页面组件1</div>`
}
const page2 = {
template: `<div style="color:'green'">页面组件2</div>`
}
const app = new Vue({
el: '.app',
components: {
"page1": page1,
"page2": page2
},
data() {
return {
page: "page1"
}
}
})
</script>
结果:
4、范例:开发一款小巧的开关按钮组件
<div class="root">
<my-btn switch-style="mini" background-color="pink" border-color="green"></my-btn>
<my-btn switch-style="normal" background-color="pink" border-color="green">
</div>
<script>
const btnComponent = {
props: ['switchStyle', 'backgroundColor', 'borderColor'],
template: `<div :style="cssBg">
<div :style="cssBtn" @click="click"></div>
</div>`,
computed: {
cssBg: {
get() {
if (this.switchStyle == 'mini') {
return `width:200px;height:50px;
backgroundColor:${this.backgroundColor};
border:1px solid ${this.borderColor};
position:relative;
border-radius:25px
`
} else {
return `width:200px;height:50px;
backgroundColor:${this.backgroundColor};
border:1px solid ${this.borderColor};
position:relative;
`
}
}
},
cssBtn: {
get() {
if (this.switchStyle == 'mini') {
return `width:50px;height:50px;
backgroundColor:black;
border-radius:25px;
position:absolute;
${this.isOpen == true ? this.left = '150px' : '0px'};
left:${this.left};
${this.backgroundColor = this.isOpen ? 'pink' : 'blue'};
`
} else {
return `width:50px;height:50px;
backgroundColor:black;
position:absolute;
${this.isOpen == true ? this.left = '150px' : '0px'};
left:${this.left}
${this.backgroundColor = this.isOpen ? 'pink' : 'blue'};
`
}
}
}
},
data() {
return {
isOpen: false,
left: '0px'
}
},
methods: {
click() {
this.isOpen = !this.isOpen
this.left = this.isOpen ? '25px' : '0px'
/* this.backgroundColor = this.isOpen ? 'pink' : 'blue' */
}
},
}
const root = new Vue({
el: '.root',
components: {
"my-btn": btnComponent
}
})
</script>
结果: