效果
先让我们看一下例子的效果吧!
v-model
我们知道 v-model 是 vue 里面的一个指令,它可以用在 input 标签上,来做数据的双向绑定,就像这样:
<input v-model="tab">
v-model 事实上是一个语法糖,你也可以这么写:
<input :value="tab" @input="tab = $event.target.value">
可以看得出来,就是传进去一个参数 :value,监听一个事件 @input 而已。
如果有这样的需求,需要在自己的组件上使用 v-model,就像这样:
<Tab v-model="tab"></Tab>
如何来实现呢?
既然已经知道 v-model 是语法糖了,
那么首先,我们可以知道在组件内得到的参数。
<!-- Tab.vue -->
<template>
<div class="tab">
<p>可以试着把这个值打印出来???</p>
{{value}}
</div>
</template>
<script>
export default {
props: {
// ↓这个就是我们能取到的参数
value: {
type: String,
default: ''
}
}
}
</script>
嗯,先把这个 value 先放着,如果要实现例子的那个 Tab,还需要传进来一组选项(options):
<!-- example.vue -->
<template>
<div>
<!-- 这里多了一个参数 ↓ -->
<Tab v-model="tab" :options="options"></Tab>
<p class="info">{{tab}}</p>
</div>
</template>
<script>
import Tab from '~/Tab';
export default {
components: {
Tab
},
data() {
return {
tab: 'bj',
options: [{
value: 'bj',
text: '北京'
}, {
value: 'sh',
text: '上海',
disabled: true
}, {
value: 'gz',
text: '广州'
}, {
value: 'sz',
text: '深圳'
}]
}
}
}
</script>
那我们就把传进来的 options 循环出来吧!
<!-- Tab.vue -->
<template>
<div class="tab">
<div
class="item"
v-for="(item, i) in options"
:key="i">
{{item.text}}
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String
},
options: {
type: Array,
default: []
}
}
}
</script>
传进来的 options 缺少些参数,我们每个选项需要 active 来标记是否是选中状态,需要 disabled 来标记是否是禁选状态,所以拷贝一个 currOptions 来补全不足参数。
另外直接改变 value 这个 props 是没有效果滴,所以拷贝一个 value 的副本,叫 currValue。
<!-- Tab.vue -->
<script>
export default {
props: {
value: {
type: String
},
options: {
type: Array,
default: []
}
},
data() {
return {
// 拷贝一个 value
currValue: this.value,
currOptions: []
}
},
mounted() {
this.initOptions();
},
methods: {
initOptions() {
// 拷贝一个 options
this.currOptions = this.options.map(item => {
return {
...item,
active: item.value === this.currValue,
disabled: !!item.disabled
}
});
}
}
}
</script>
?接下来再在选项上绑定击事件就 OK 了。
既然知道父组件会接受 input 事件,那我们就只需要 this.$emit('input', this.currValue);
就好了。
<!-- Tab.vue -->
<template>
<div class="tab">
<div
class="item"
v-for="(item, i) in options"
:key="i"
@click="onTabSelect(item)">
<!-- ↑ 这里绑定了一个事件! -->
{{item.text}}
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String
},
options: {
type: Array,
default: []
}
},
data() {
return {
currValue: this.value,
currOptions: []
}
},
mounted() {
this.initOptions();
},
methods: {
initOptions() {
this.currOptions = this.options.map(item => {
return {
...item,
active: item.value === this.currValue,
disabled: !!item.disabled
}
});
},
// 添加选中事件
onTabSelect(item) {
if (item.disabled) return;
this.currOptions.forEach(obj => obj.active = false);
item.active = true;
this.currValue = item.value;
// 发布 input 事件,↓ 父组件如果有 v-model 就会监听到的。
this.$emit('input', this.currValue);
}
}
}
</script>
剩下的补上点样式还有 watch 下 value 和 options 的变化就可以了,最后贴上完整代码。
完整代码
<!-- example.vue -->
<template>
<div>
<Tab v-model="tab" :options="options"></Tab>
<p class="info">{{tab}}</p>
</div>
</template>
<script>
import Tab from '~/Tab';
export default {
components: {
Tab
},
data() {
return {
tab: 'bj',
options: [{
value: 'bj',
text: '北京'
}, {
value: 'sh',
text: '上海',
disabled: true
}, {
value: 'gz',
text: '广州'
}, {
value: 'sz',
text: '深圳'
}]
}
}
}
</script>
<style lang="less" scoped>
.info {
margin-left: 50px;
font-size: 30px;
}
</style>
<!-- Tab.vue -->
<template>
<div class="tab">
<div
class="item"
v-for="(item, i) in currOptions"
:class="item | tabItemClass"
:key="i"
@click="onTabSelect(item)">
{{item.text}}
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String
},
options: {
type: Array,
default: []
}
},
data() {
return {
currValue: this.value,
currOptions: []
}
},
mounted() {
this.initOptions();
},
methods: {
initOptions() {
this.currOptions = this.options.map(item => {
return {
...item,
active: item.value === this.currValue,
disabled: !!item.disabled
}
});
},
onTabSelect(item) {
if (item.disabled) return;
this.currOptions.forEach(obj => obj.active = false);
item.active = true;
this.currValue = item.value;
this.$emit('input', this.currValue);
}
},
filters: {
tabItemClass(item) {
let classList = [];
if (item.active) classList.push('active');
if (item.disabled) classList.push('disabled');
return classList.join(' ');
}
},
watch: {
options(value) {
this.initOptions();
},
value(value) {
this.currValue = value;
}
}
}
</script>
<style lang="less" scoped>
.tab {
@borderColor: #ddd;
@radius: 5px;
width: 100%;
margin: 50px;
overflow: hidden;
position: relative;
.item {
padding: 10px 50px;
border-top: 1px solid @borderColor;
border-left: 1px solid @borderColor;
border-bottom: 1px solid @borderColor;
font-size: 30px;
background-color: #fff;
float: left;
user-select: none;
cursor: pointer;
transition: 300ms;
&:first-child {
border-top-left-radius: @radius;
border-bottom-left-radius: @radius;
}
&:last-child {
border-right: 1px solid @borderColor;
border-top-right-radius: @radius;
border-bottom-right-radius: @radius;
}
&.active {
color: #fff;
background-color: red;
}
&:hover {
color: #fff;
background-color: #f06;
}
&.disabled {
color: #fff;
background-color: pink;
cursor: no-drop;
}
}
}
</style>
最后送上官网的链接→ 传送门
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。