神了!!看完这篇文章我不仅学会了手撸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,
			}
		}
	}
</script>

demo下载地址

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
Vue是一种流行的JavaScript框架,用于构建用户界面。在Vue中,父子组件之间递数据是一种常见的需求。以下是一种常用的方法来实现父子组件之间的数据递: 1. Props(属性):父组件可以通过props属性向子组件递数据。在父组件中,通过在子组件标签上绑定属性的方式递数据。在子组件中,可以通过props选项接收并使用这些数据。 父组件: ```html <template> <div> <child-component :message="parentMessage"></child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { parentMessage: 'Hello from parent component' }; } } </script> ``` 子组件: ```html <template> <div> <p>{{ message }}</p> </div> </template> <script> export default { props: ['message'] } </script> ``` 2. $emit(自定义事件):子组件可以通过$emit方法触发自定义事件,并将需要递的数据作为参数递给父组件。在父组件中,通过在子组件标签上监听自定义事件的方式接收数据。 父组件: ```html <template> <div> <child-component @child-event="handleChildEvent"></child-component> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, methods: { handleChildEvent(data) { console.log(data); // 在这里处理子组件递的数据 } } } </script> ``` 子组件: ```html <template> <div> <button @click="emitEvent">触发事件</button> </div> </template> <script> export default { methods: { emitEvent() { this.$emit('child-event', 'Hello from child component'); // 触发自定义事件,并递数据给父组件 } } } </script> ``` 以上是Vue中实现父子组件之间递数据的两种常用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SNiFe_Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值