神了!!看完這篇文章我不僅學會了手擼vue三開關組件,還搞懂了父子組件傳值
- 引子
- 前置知識
- 什么是vue組件
- 父子組件傳值
- 父傳子
- 子傳父
- model選項的引入
- 三開關組件(three-switch)
- 第一版:vant版
- 第二版:影片流暢版
- 第三版:邏輯優化版
- 第四版:傳值優化版
引子
這段時間在做公司的專案的時候遇到篩選功能的撰寫,分為三種狀態[未評價,已評價,全部],平時其實都是做下拉框來解決這個問題,但是在這個篩選彈窗里做下拉框整體又不太美觀,同事最開始使用的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下載地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/296896.html
標籤:其他
