前言
最近在嘗試寫幾個UI元件,並通過閱讀element-ui的原始碼,與其反覆比較,然後認真思考,最後總結出一些自己的一些心得和體會。在造輪子的過程中,既鞏固了html
,css
,js
基礎,又加深了對vue
原始碼的理解,更重要的是給了我一個溫習和實踐所學過的設計模式和思想的機會,來編寫更加優雅的程式碼。
本文會介紹一些自己總結的,關於如何用vue
優雅地設計和編碼ui元件的指導原則,更多的是希望給大家提供一些選擇和參考,而不是墨守成規的條例。
下拉選單和輪播的主要構成(熟悉的朋友可以直接跳過看結論)
下拉選單
一個簡單的下拉選單主要是一個輸入框和列表構成
在vue
中呼叫的方式大致是這樣的:
<yy-select v-model="value" placeholder="請選擇">
<yy-option v-for="item in options" :value="item.value" :label="item.label">
</yy-option>
</yy-select>複製程式碼
這裡有兩個元件,分別是yy-select
,yy-option
,在內部實現中,yy-option
可以是一個<li>
標籤,yy-select
則包含一個<input>
標籤,並通過<slot>
將<li>
插入到一個<ul>
中。
輪播圖
輪播圖的原理是通過將多個div
放到一個父標籤div
(寬度width
)中,並將父標籤div
設定成overflow:hidden
,將當前顯示的div
設定成transformX(0)
,上一個div
設定成transformX(-width)
,下一個div
設定成transformX(width)
,再利用過渡即可實現切換的效果
一個簡單的輪播圖是由上面提到的父標籤div
,以及一些附加元素,比如左右兩邊放個左右切換按鈕,下面放一排指定元素切換按鈕構成
在vue
中呼叫的方式大致是這樣的:
<yy-carousel>
<yy-carousel-item v-for="item in 4" >
<h3>{{item}}</h3>
</yy-carousel-item>
</yy-carousel>複製程式碼
可以發現呼叫方式和下拉選單很類似,在內部實現中,yy-carousel-item
可以是一個<div>
標籤,yy-carousel
則通過<slot>
將這些<div>
包含進來,並新增一些<button>
之類的標籤
指導原則
接下來將開始我的表演
子元件在created
鉤子中建立“父子”關係
這裡要先解釋一下vue
插槽原理:通過閱讀vue
原始碼,我們可以知道,<yy-select><yy-option></yy-option></yy-select>
會建立兩個VNode
,就像那模板程式碼給人的感覺一樣,這個兩個VNode
是父子關係。然而,假設yy-select
元件中的<template>
是下面這樣的:
<template>
<yy-input></yy-input>
<yy-scroll-bar>
<slot></slot>
</yy-scroll-bar>
</template複製程式碼
那麼,在vue
對VNode
進行patch
的時候,yy-option
作為插槽被插到的是yy-scroll-bar
裡面,此時yy-option
的vue例項的父例項(也就是通過this.$parent
獲取到的)是yy-scroll-bar
的vue例項,而不是yy-select
的vue例項。
在寫元件的過程中,yy-select
例項充當一個父親,負責yy-input
和yy-option
這兩個元件的例項相互通訊,這個時候可以人為構造一個父子關係,即:在yy-option
中找到yy-select
這個例項,並設定this.parent
為該例項,同理在yy-select
中找到所有的yy-option
例項,並賦值為一個陣列:this.options
。這個過程可以在yy-option
例項的created
鉤子函式中完成。
只在內部改變自己的狀態
場景:在下拉選單的輸入框裡面輸入文字,只有匹配到的option
才會顯示在列表裡。
做法:在yy-select
元件中監聽yy-input
的input
事件,然後寫個方法判斷this.options
裡面有哪些滿足要求,並設定this.options[i].visible=True
這種在父元件中直接修改子元件屬性的做法有諸多不好的地方。更合理的做法是將判斷的方法放到yy-option
中,通過computed watch
監聽父元件的一個比方說叫inputContent
的屬性,又或者利用事件訂閱讓父元件通過分發事件,來呼叫yy-option
的方法 ,讓yy-option
在自己內部改變自己的visible
屬性(有種類似vuex
的感覺,元件可以改變store
,但不能在元件的方法裡直接去改,得發個action
,在規定的mutation
裡面改)
data設定原子屬性,computed設定派生屬性
這一條和上一條形式上一致,只是看問題的角度不同。vue
的核心思想是元件化和資料驅動,這意味著對於所有的顯示(class
,style
),都要通過資料驅動,而不是直接操作dom
。我在寫UI元件的時候一個主要的思考就是如何能夠減少data
中的變數,畢竟為每一個狀態設定一個變數去控制很冗餘。比如說yy-option
,根據輸入框內容決定是否顯示需要visible
,滑鼠點選了背景要變藍需要selected
,通過滑鼠或者鍵盤上下鍵停留背景要變灰hover
,這樣就有三個變數放在data
裡面,更糟糕的是如果你在yy-select
父元件中通過邏輯判斷後直接修改這些變數值,這是一種非常ugly的做法。我的建議是在子元件的data
中設定一些原子屬性,比如該option
的value label
以及在父元件的this.options
中的index
;在父元件中的data
中設定value hoverIndex
,那麼子元件中類似hover
這樣的派生變數就可以放到computed
裡面,由hoverIndex
和index
派生得到,它從原來的由父元件野蠻入侵式地修改變成了現在優雅地依賴於父元件
能用css控制的屬性、狀態就不要用js
說明:一般下拉選單的輸入框後面都會跟一個向下的箭頭之類的icon
,還可以前後各放一些修飾的元素,按照元件化的設計思想,我們將其封裝成一個對外暴露的<yy-input>
場景:滑鼠移動到下拉選單的yy-input
的<input>
輸入框,整個yy-input
的邊界顯示淺藍色;點選按鈕顯示灰色;預設是淺灰色
方法:將<input>
和icon
放到一個<div>
裡面,監聽<input>
的hover focus
事件,來改變isHover isFocus
變數的值,從而改變<div>
的style
或者class
,達到修改border
的目的
你會發現在data
裡面新增了很多額外的變數,在很多業務邏輯裡面也要記得新增修改這些變數的語句(點選輸入框後邊界變藍且彈出列表,此時isFocus=true
,點選列表的某個option
後,記得設定isFocus=false
,要恢復成預設顏色)。有沒有一種方法可以通過監聽<input>
的事件來修改整個邊界的顏色?
答案是css
。其實只要將icon
絕對定位到輸入框裡面就好了,這時候整個邊界的顏色,就是輸入框邊界的顏色……
結語
總的來說,上面的幾條指導原則優化的目標主要是變數和通訊。如何確定每個元件內部所需要的變數,以及這些元件內部變數如何通訊,是用vue
優雅地編寫UI
元件的關鍵。
要把一件事情做出來是簡單的,比如寫個下拉選單或者輪播圖,原理都不難。只是當考慮到API
的設計,程式碼的質量,要關注的可能就不僅僅是一個一個孤立的知識點,
而是,一個面。