Vue2实现子组件改变父子组件的数据(TS版)
制作组件的时候大部分情况需要在子组件内部改变父组件的数组,但是由于vue的限制,在子组件改变父组件的state会报错,本文介绍如何利用v-model实现父子组件的数据双向绑定。本文环境- vue2(2.5+)- vue-class-component- vue-property-decorator大致思路是用input的value承载父组件的数据,然后使用v-mod
·
制作组件的时候大部分情况需要在子组件内部改变父组件的数据,但是由于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
更多推荐
已为社区贡献5条内容
所有评论(0)