Vue2实现子组件改变父子组件的数据

简介: 制作组件的时候大部分情况需要在子组件内部改变父组件的数据,但是由于vue的限制,在子组件改变父组件的state会报错,本文介绍如何利用v-model实现父子组件的数据双向绑定。

制作组件的时候大部分情况需要在子组件内部改变父组件的数据,但是由于vue的限制,在子组件改变父组件的state会报错,本文介绍如何利用v-model实现父子组件的数据双向绑定。

本文环境
- 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

目录
相关文章
|
2月前
|
JavaScript
Vue中ref创建_基本类型的响应式数据,在Vue2的年代,数据配在data里,Vue3的区别是不把响应数据写在data里,那个数据是响应式的用ref包一下,let name = ref(“张三“)
Vue中ref创建_基本类型的响应式数据,在Vue2的年代,数据配在data里,Vue3的区别是不把响应数据写在data里,那个数据是响应式的用ref包一下,let name = ref(“张三“)
|
2月前
|
前端开发
Vue2和Vue3的区别,在setup中定义的数据,在data(){return中能否定义到},在setup我们不能用this,写在return中可以用this,但是不能在setup否则会报错
Vue2和Vue3的区别,在setup中定义的数据,在data(){return中能否定义到},在setup我们不能用this,写在return中可以用this,但是不能在setup否则会报错
|
24天前
|
JavaScript
Vue学习之--------深入理解Vuex之多组件共享数据(2022/9/4)
这篇文章通过一个实际的Vue项目案例,演示了如何在Vuex中实现多组件间共享数据。文章内容包括在Vuex的state中新增用户数组,创建Person.vue组件用于展示和添加用户信息,以及在Count组件中使用Person组件操作的数据。通过测试效果展示了组件间数据共享和状态更新的流程。
Vue学习之--------深入理解Vuex之多组件共享数据(2022/9/4)
|
28天前
|
JavaScript 前端开发
Vue学习之--------Vue中收集表单数据(使用v-model 实现双向数据绑定、代码实现)(2022/7/18)
这篇文章介绍了Vue中使用v-model实现表单数据收集的方法,包括基础知识、代码实例和测试效果,并提供了一些额外建议。
Vue学习之--------Vue中收集表单数据(使用v-model 实现双向数据绑定、代码实现)(2022/7/18)
|
28天前
|
JavaScript API
Vue学习之--------列表排序(ffilter、sort、indexOf方法的使用)、Vue检测数据变化的原理(2022/7/15)
这篇博客文章讲解了Vue中列表排序的方法,使用`filter`、`sort`和`indexOf`等数组方法进行数据的过滤和排序,并探讨了Vue检测数据变化的原理,包括Vue如何通过setter和数组方法来实现数据的响应式更新。
Vue学习之--------列表排序(ffilter、sort、indexOf方法的使用)、Vue检测数据变化的原理(2022/7/15)
|
28天前
|
JavaScript
Element - Vue使用slot-scope和v-for遍历数据为树形表格
这篇文章介绍了在Vue中使用`slot-scope`和`v-for`指令来遍历数据并将其渲染为树形表格的方法。
23 0
Element - Vue使用slot-scope和v-for遍历数据为树形表格
|
1月前
|
JavaScript 数据处理
如何使用 Vue.js 将数据对象的值放入另一个数据对象中?
如何使用 Vue.js 将数据对象的值放入另一个数据对象中?
|
1月前
|
JavaScript UED
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。
强制 Vue 重新渲染组件的5种方法,解决你开发过程中数据和视图无法同步的Bug。
|
2月前
|
开发框架 JavaScript 前端开发
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
|
2月前
|
存储 开发框架 前端开发
在Winform中直接录入表格数据和在Vue&Elment中直接录入表格数据的比较
在Winform中直接录入表格数据和在Vue&Elment中直接录入表格数据的比较