先讓咱們看一下例子的效果吧!
咱們知道 v-model 是 vue 裏面的一個指令,它能夠用在 input 標籤上,來作數據的雙向綁定,就像這樣:html
<input v-model="tab">
v-model 事實上是一個語法糖,你也能夠這麼寫:vue
<input :value="tab" @input="tab = $event.target.value">
能夠看得出來,就是傳進去一個參數 :value,監聽一個事件 @input 而已。
若是有這樣的需求,須要在本身的組件上使用 v-model,就像這樣:less
<Tab v-model="tab"></Tab>
如何來實現呢?
既然已經知道 v-model 是語法糖了,
那麼首先,咱們能夠知道在組件內獲得的參數。ide
<!-- Tab.vue --> <template> <div class="tab"> <p>能夠試着把這個值打印出來???</p> {{value}} </div> </template> <script> export default { props: { // ↓這個就是咱們能取到的參數 value: { type: String, default: '' } } } </script>
嗯,先把這個 value 先放着,若是要實現例子的那個 Tab,還須要傳進來一組選項(options):ui
<!-- 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 循環出來吧!this
<!-- 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。spa
<!-- 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 的變化就能夠了,最後貼上完整代碼。code
<!-- 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>
最後送上官網的連接→ 傳送門component