神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值
Posted SNiFe_Blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值相关的知识,希望对你有一定的参考价值。
神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值
引子
这段时间在做公司的项目的时候遇到筛选功能的编写,分为三种状态[未评价,已评价,全部]。平时其实都是做下拉框来解决这个问题,但是在这个筛选弹窗里做下拉框整体又不太美观。同事最开始使用的vant的switch组件,但是这个组件只有true和false两种状态,并没有完全的满足需求,我找了大部分框架都没找到threeSwitch,于是自己动手撸了一个。
我们先来看看效果吧:
在正式讲解三开关组件之前,先介绍几个前置知识
前置知识
什么是vue组件
组件是可复用的 Vue 实例, 把一些公共的模块抽取出来,然后写成单独的的工具组件或者页面,在需要的页面中就直接引入即可那么我们可以将其抽出为一个组件进行复用。例: 页面头部、侧边、内容区,尾部,上传图片,等多个页面要用到一样的就可以做成组件,提高了代码的复用率。(直接引用啦)
父子组件传值
只写组件当然不行,组件之间的通信才是灵魂
父传子
在子组件使用props选项,接收父组件传来的值
子组件child.vue
<template>
<div>
<h3>父组件传值</h3>
<div>
{{father}}
</div>
</div>
</template>
<script>
export default {
props:{
father:{
type:String,
default:'空'
}
}
}
</script>
父组件father.vue
<template>
<div style="text-align: center;">
<child :father="father"></child>
<button @click="change">点击传值</button>
</div>
</template>
<script>
import child from './components/child/child.vue'
export default {
components: {
child
},
data() {
return {
father: '空'
}
},
methods:{
change(){
this.father='这是父组件传来的值'
}
}
}
</script>
子传父
使用emits方法向父组件传值
子组件child.vue
<template>
<div>
<h3>子组件传值</h3>
<button @click="change">点击传值</button>
</div>
</template>
<script>
export default {
methods:{
change(){
this.$emit('change','子组件传值')
}
}
}
</script>
父组件father.vue
<template>
<div style="text-align: center;">
<child @change='change'></child>
<div>{{child}}</div>
</div>
</template>
<script>
import child from './components/child/child.vue'
export default {
components: {
child
},
data() {
return {
child: '空'
}
},
methods:{
change(data){
this.child=data
}
}
}
</script>
model选项的引入
通过上面的基本了解,现在我们已经知道如何进行父子组件间的通信了,但是对于一些组件,比如表单元素,我们是需要值的双向绑定的,通过model选项的引入,我们可以实现值的双向绑定,当然要实现父组件的动态传值,子组件是需要使用watch来监听父组件的传值变化的
父组件father.vue
<template>
<div id="app" style="text-align: center;">
<h3>model双向绑定-父组件</h3>
<div>
{{father}}
</div>
<button @click="change">点击改变值</button>
<child v-model="father"></child>
</div>
</template>
<script>
import child from './components/child/child.vue'
export default {
components: {
child
},
data() {
return {
father: 0
}
},
methods:{
change(){
this.father+=1
}
}
}
</script>
子组件child.vue
<template>
<div>
<h3>model双向绑定-子组件</h3>
<button @click="change">点击改变值</button>
<div>
{{child}}
</div>
</div>
</template>
<script>
export default {
props:{
father:{
type:Number,
default:0
}
},
data(){
return{
child:0
}
},
created() {
this.child=this.father
},
model:{
prop:'father',
event:'change'
},
watch:{
father(newVal){
this.child=newVal
}
},
methods:{
change(){
this.child+=1
this.$emit('change',this.child)
}
}
}
</script>
三开关组件(three-switch)
终于讲到了三开关组件,最开始写这个组件的时候我是参考了vant,但是这样写出来圆点和进度条的滑动有延迟,最后我改了大概四版的样子,最后写出了自己最满意的那一版,下面分别这四版的演变,希望能给大家一些启示吧
第一版:vant版
这一版主要参考了vant的动画,但是逻辑二开关与三开关不太一样,逻辑上参考较少,动画的思路大致如下:
将组件分为两层:第一层背景(固定长宽的弧形框)、第二层滑动条与圆点。然后让滑动条与圆点同时滑动,但这样其实是有延迟的,二者的收缩速率并没有办法保持一致。代码在更改后未保存,大家可以自己实现一下。
第二版:动画流畅版
这一版在动画上做了改进,灵感来源于我的好兄弟,在参考他的思路后,我写出了第二版,这一版动画非常的流畅,可以说是已经实现了文章最初的gif图中的样子,但是还有一些缺陷。现在讲讲如何改进的动画:
将之前的两层结构换成了三层结构:第一层背景、第二层滑动条、第三层圆点。然后给圆点加上了 {dispaly:absolute;right:0;},非常简单的改动,但是实现了动画的流畅。
第三版:逻辑优化版
在此之前的版本中,逻辑其实都很复杂,判断做的很多,基本的逻辑是:
- 如果圆点是在最左边,那么滑动的方向只能向右;
- 如果圆点是在最右边,那么滑动的方向只能向左;
- 如果圆点是在中间,那么根据上一次滑动的方向来判断向哪边滑动,如果是上一次是向右滑动,那么这一次也向右,如果上一次是向左滑动,那么这一次也向左滑动。
但是这样的话每一次滑动其实要做三次判断,这样的效率是很低的,虽然现在计算机硬件很牛,这样的运算很快就能计算出来,但是我还是希望能够将这种变化优化到每次最多只需要判断两次,大多数情况下只需要判断两次,且简化运算。想了很久后,我的思路是这样的:
- 将三种状态分别定义为了数字:-1,0,1
- 每次更新状态,先判断方向,若圆点在最左边或者最右边就换向,使用if-else减少判断次数
- 判断三开关状态:-1/0/1,若方向为右,则状态+1,若方向为左,则状态-1
- 将滑动条的长度变为动态更新,将其长度与变量stripWidth绑定,这样更新stripWidth即可更行滑动条长度,使其滑动
- 向父组件传输更新的状态
函数代码如下:
changeStatus() {
if(this.checkValue===1)this.direct=0
else if(this.checkValue===-1)this.direct=1
this.checkValue=this.direct===1?this.checkValue+1:this.checkValue-1
this.stripWidth = (2+this.checkValue) * this.size
this.$emit("change", this.checkValue)
}
第四版:传值优化版
在之前的版本中其实我一直没有成功实现动态更新传值,在这个版本中我引入了watch和model实现了传值的优化。
到此其实已经实现了真正开发一个组件库中的组件的80%的工作,若要开发组件库,还需要将此代码封装,以提高引用的效率和后期维护。
接下来细致的描述一下组件编写的过程:
- 编写大致的框架,在这个组件中,我首先会编写如下代码:
<template>
<div class="root">
<div class="strip">
<div class="node">
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped lang="scss">
</style>
- 编写大致的样式
<style scoped lang="scss">
.root {
position: relative;
display: inline-block;
box-sizing: content-box;
font-size: 30px;
background-color: white;
border: 1px solid rgba(221, 221, 221, 1.0);
cursor: pointer;
}
.node {
position: absolute;
top: 0;
right: 0;
background-color: white;
border-radius: 100%;
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, 0.05),
0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05);
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
}
.strip {
position: absolute;
top: 0;
left: 0;
background: rgba(95, 184, 120, 1);
transition: width 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
}
</style>
- 构思逻辑,然后编写方法,方法的思路在上面已经提到了,最终完善组件,组件代码如下:
threeSwitch.vue
<template>
<div class="root" @click="changeStatus"
:style="{ width: rootWidth + 'em', height: height + 'em',borderRadius:radius+'em' }">
<div class="strip" :style="{ width: stripWidth + 'em', height: height + 'em',borderRadius:radius+'em' }">
<div class="node" :style="{ width: nodeWidth + 'em', height: height + 'em' }">
</div>
</div>
</div>
</template>
<script>
export default {
props: {
size: {
type:String,
default:1
},
check: {
type:Number,
default:-1
}
},
data() {
return {
ismid: true,
isright: false,
rootWidth: 3,
height: 1,
stripWidth: 1,
nodeWidth: 1,
radius: 1,
direct: 1,
checkValue: -1
}
},
methods: {
changeStatus() {
if(this.checkValue===1)this.direct=0
else if(this.checkValue===-1)this.direct=1
this.checkValue=this.direct===1?this.checkValue+1:this.checkValue-1
this.stripWidth = (2+this.checkValue) * this.size
this.$emit("change", this.checkValue)
}
},
model: {
prop: 'check',
event: 'change'
},
created() {
if(this.check==1)this.direct = 0
this.checkValue=this.check
this.stripWidth = (2+this.checkValue) * this.size
this.rootWidth *= this.size
this.height *= this.size
this.nodeWidth *= this.size
this.radius *= this.size
}
}
</script>
<style scoped lang="scss">
.root {
position: relative;
display: inline-block;
box-sizing: content-box;
font-size: 30px;
background-color: white;
border: 1px solid rgba(221, 221, 221, 1.0);
cursor: pointer;
}
.node {
position: absolute;
top: 0;
right: 0;
background-color: white;
border-radius: 100%;
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, 0.05),
0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05);
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
}
.strip {
position: absolute;
top: 0;
left: 0;
background: rgba(95, 184, 120, 1);
transition: width 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
}
</style>
引用组件的示例如下:
<template>
<div id="app" style="text-align: center;">
<three-switch v-model="check" size="4"></three-switch>
<div style="font-size: 100px;">{{check}}</div>
</div>
</template>
<script>
import threeSwitch from './components/threeSwitch/threeSwitch.vue'
export default {
components: {
threeSwitch,
},
data() {
return {
check: 1,
}
}
}
以上是关于神了!!看完这篇文章我不仅学会了手撸vue三开关组件,还搞懂了父子组件传值的主要内容,如果未能解决你的问题,请参考以下文章
看完这篇文章保你面试稳操胜券 ——(必考题)javaScript 篇