制作组件的时候大部分情况需要在子组件内部改变父组件的数据,但是由于vue的限制,在子组件改变父组件的state会报错,本文介绍如何利用v-model实现父子组件的数据双向绑定。
如需查看常规 Vue 环境的教程,移步 => vue如何像Element那样封装组件

本文环境
- vue2(2.5+)
- vue-class-component
- vue-property-decorator

大致思路是用input的value承载父组件的数据,然后使用v-model实现双向绑定数据。

首先把父组件声传入的值声明为prop

// vue-class-component 写法
@Component({
  model: {
    prop: 'val'
  }
})
export default class Slidr extends Vue {
  // ...
  @Prop()
  val: number
  // ...
}

然后在子组件添加一个input标签,然后将props绑定上去。

// 将val绑定在input的value上,并给input事件添加函数
<input style="display: none;"
  :value="val"
  @input="changeVal"/>

然后要改变父组件绑定的值的时候,用自定义事件去改变

// 当input的值改变时触发
@Emit('input')
  changeVal(val: number) { }
// 要改变父组件的值时使用自定义事件函数,这样不会报错
this.changeVal(5)

还可以封装当值改变时的change事件,绑定在父组件上就可在val改变时触发

// 自定义事件change
@Emit('change')
  outVal(val: number) { }
// 改写事件触发时执行的函数
@Emit('input')
  changeVal(val: number) { this.outVal(val) }

然后就可以自己封装组件了


下面是一个数据双向绑定的slider组件,可以参考一下

<template>
<section class="slider-box"
ref="slider"
:style="{
  width: wid,
  opacity: disabled ? 0.5 : 1}">

  <div class="slider-line"
  ref="line"
  :style="lineSty"></div>

  <div class="slider-line_choose"
  :style="lineChooseSty"></div>

  <transition name="tag">
    <div class="tag"
    v-if="showTag && isShowTag"
    :style="tagStyle">{{tagPrompt || val}}</div>
  </transition>


  <div
  class="slider-btn"
  @touchstart="touchStart"
  @touchend="touchEnd"
  :class="{
    enter: isTouch,
    out: !isTouch
  }"
  :style="[{
    borderColor: chooseColor},
    btnPosit]"></div>

  <!-- 使用input做到prop数据的双向绑定,在子组件改变父组件传入的prop的值不报错 -->
  <input class="slider-val"
  :value="val"
  @input="changeVal"/>
</section>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop, Emit } from 'vue-property-decorator'
import { throttle } from 'lodash'
import $ from 'jquery'

@Component({
  components: {
  },
  model: {
    prop: 'val'
  },
  filters: {
  }
})

export default class Slidr extends Vue {
  isTouch: boolean = false // 是否触摸选中
  onTouch :any = throttle(this.onTouchFn, 50) // 节流触摸事件
  showTag: boolean = false
  startValue: number = 0 // 检测触摸开始和结束的值是否一样
  sliderInfo: any = {
    wid: 0, // slider的长度
    hei: 0, // line的高度
    left: 0, // slider在文档中的位置
    right: 0, // slider在文档中的位置
  }
  @Prop({default: 2})
  digit: number // 数据精度,小数点后几位
  @Prop({default: '100%'})
  wid: String // slider长度
  @Prop({default: '2px'})
  hei: String // slider高度
  @Prop({default: 'rgba(223,228,237,1)'})
  color: String // 未选中line背景色
  @Prop({default: 'rgba(40,164,186,1)'})
  chooseColor: String
  @Prop()
  val: number // btn所在位置的值,用v-modal绑定
  @Prop({default: 0})
  minVal: number
  @Prop({default: 1})
  maxVal: number
  @Prop({default: () => { return{backgroundColor:'rgba(0,0,0,0.8)'}}})
  tagStyle: String // tag背景色
  @Prop({default: false})
  tagPrompt: any // tag显示内容
  @Prop({default: true})
  isShowTag: Boolean
  @Prop({default: false})
  disabled: Boolean // 是否禁用
  @Emit('input')
  changeVal(val: number) { this.outVal(val) }
  @Emit('change')
  outVal(val: number) { }
  @Emit('changeEnd') // 结束事件返回两个参数,结束时的值,位置是否改变
  endVal(val: number, sta: boolean) { }
  @Emit('changeStart')
  startVal(val: number) { }
  created () {
  }
  mounted () {
    // 存储slider在页面的位置及大小信息
    const slider: any = $(this.$refs.slider),
      line: any = $(this.$refs.line)

    this.sliderInfo = {
      wid: slider.width(),
      hei: line.height(),
      left: slider.offset().left,
      right: slider.width() + slider.offset().left
    }
  }
  destroyed () {
  }
  get lineSty() {
    return {
      height: this.hei,
      backgroundColor: this.color,
      borderRadius: this.hei
    }
  }
  get btnPosit() {
    return {
      left: ((this.val / (this.maxVal - this.minVal)) * 100) + '%'
    }
  }
  get lineChooseSty() {
    let wid = ((this.val / (this.maxVal - this.minVal)) * 100) + '%'

    return {
      width: wid,
      height: this.hei,
      backgroundColor: this.chooseColor
    }
  }
  touchStart() {
    if(this.disabled){ return }
    window.addEventListener('touchmove', this.onTouch)
    this.startValue = this.val
    this.startVal(this.val)
    this.isTouch = true
    this.showTag = true }
  touchEnd() {
    if(this.disabled){ return }
    window.removeEventListener('touchmove', this.onTouch)
    const sta = this.startValue === this.val

    this.endVal(this.val, sta)
    this.isTouch = false
    this.showTag = false }
  onTouchFn(e) :void{
    if(!this.isTouch){ return }
    const pos: number = e.targetTouches[0].pageX

    if(pos >= this.sliderInfo.right) {
      this.changeVal(this.maxVal)
      return }
    else if(pos <= this.sliderInfo.left) {
      this.changeVal(this.minVal)
      return }
    else {
      const ratio: number = (pos - this.sliderInfo.left) / this.sliderInfo.wid,
        val = this.maxVal * ratio

      this.changeVal(Number(Number(val).toFixed(this.digit)))
    }
  }
}
</script>

<style lang="stylus" scoped>
@import '../../static/css/common.styl'

// tag动画
.tag-enter-active, .tag-leave-active
  transition opacity .5s
.tag-enter, .tag-leave-to
  opacity 0


// 按钮动画
.enter
  animation sliderEnter .5s
  animation-fill-mode forwards
.out
  animation sliderOut .5s
  animation-fill-mode forwards
@keyframes sliderEnter
  0% 
    transform scale(1) translate(-50%, -50%)
  100%
    transform scale(1.2) translate(-50%, -50%)
@keyframes sliderOut
  0% 
    transform scale(1.2) translate(-50%, -50%)
  100%
    transform scale(1) translate(-50%, -50%)

.slider-box
  position relative
  display flex
  align-items center
  height 50px
  .slider-line
    position absolute
    top 50%
    transform translateY(-50%)
    width 100%
  .slider-line_choose
    position absolute
    top 50%
    transform translateY(-50%)
    width 100%
    transition all .2s ease
  .slider-btn
    position absolute
    top 50%
    vertical-align middle
    transition all .05s ease
    width 13px
    height 13px
    border 1.5px solid
    border-radius 50%
    background #fff
  .slider-val
    display none
  .tag
    position absolute
    left 50%
    transform translate(-50%, -60px)
    padding 5px 10px
    border-radius 5px
</style>

文章原址:http://blog.csdn.net/qq_25243451/article/details/78664354

Logo

前往低代码交流专区

更多推荐