用vue優雅地編寫UI元件的幾條指導原則

雲灬遊發表於2019-03-04

前言

最近在嘗試寫幾個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-selectyy-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複製程式碼

那麼,在vueVNode進行patch的時候,yy-option作為插槽被插到的是yy-scroll-bar裡面,此時yy-optionvue例項的父例項(也就是通過this.$parent獲取到的)是yy-scroll-barvue例項,而不是yy-selectvue例項
在寫元件的過程中,yy-select例項充當一個父親,負責yy-inputyy-option這兩個元件的例項相互通訊,這個時候可以人為構造一個父子關係,即:在yy-option中找到yy-select這個例項,並設定this.parent為該例項,同理在yy-select中找到所有的yy-option例項,並賦值為一個陣列:this.options。這個過程可以在yy-option例項的created鉤子函式中完成。

只在內部改變自己的狀態

場景:在下拉選單的輸入框裡面輸入文字,只有匹配到的option才會顯示在列表裡。
做法:在yy-select元件中監聽yy-inputinput事件,然後寫個方法判斷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中設定一些原子屬性,比如該optionvalue label以及在父元件的this.options中的index;在父元件中的data中設定value hoverIndex,那麼子元件中類似hover這樣的派生變數就可以放到computed裡面,由hoverIndexindex派生得到,它從原來的由父元件野蠻入侵式地修改變成了現在優雅地依賴於父元件

能用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的設計,程式碼的質量,要關注的可能就不僅僅是一個一個孤立的知識點,
而是,一個面。

相關文章