如何構建運行良好的Vue組件
很少有人最初編寫Vue組件時打算將其開源。我們大多數人都是從自己編寫組件開始的——我們有一個問題,然后決定通過構建一個組件來解決它。有時我們發現自己想要在代碼庫的新位置解決相同的問題,因此我們使用組件并對其進行重構,使其可重用。然后我們想在一個不同的項目中使用它,所以我們把它轉移到一個獨立的包中。然后我們想“嘿,為什么不把這個分享給全世界呢?”于是我們開源了這個組件。
一方面,這意味著對于任何在Vue工作的人來說,都可以獲得大量且不斷增長的開源組件,這很 piece。
另一方面,因為這些組件中的大多數是從特定的情況而來的,并且不是所有人都有跨多重環境重用組件的設計經驗,所以這些組件中的許多東西都不能很好地與Vue生態系統配合使用。
“很好”是什么意思?從高層次上講,這意味著行為表現對于Vue開發人員來說很自然,并且易于擴展和集成到任何類型的應用程序中。
在探索了廣泛的開源組件之后,下面幾點,我認為下面是如何制作一個良好運行的Vue組件方式:
- 實現v-model兼容性
- 事件透明化
- 為正確的元素分配屬性
- 接受瀏覽器的鍵盤導航規范
- 使用事件優先于回調
- 限制組件樣式
實現`v-model`兼容性
對于表單字段的組件使其成為慣用的最重要方法之一就是要支持v-model。 根據官方文檔介紹,v-model本質上就是語法糖,即利用v-model綁定數據后,其實就是既綁定了數據,又添加了一個input事件監聽,如下:
自定義事件也可以用于創建支持 v-model 的自定義輸入組件。記住:
- <input v-model="searchText">
等價于:

當用在組件上時,v-model 則會這樣:

為了讓它正常工作,這個組件內的 <input>必須:
- 將其 value attribute 綁定到一個名叫 value 的 prop 上
- 在其 input 事件被觸發時,將新的值通過自定義的 input 事件拋出
寫成代碼之后是這樣的:

現在 v-model 就應該可以在這個組件上完美地工作起來了:
- <custom-input v-model="searchText"></custom-input>
事件透明化
為了實現v-model,組件需要實現input事件。但其他事件呢?比如點擊事件,鍵盤處理等等?雖然原生事件以 HTML 的形式冒泡,但是 Vue 的事件處理在默認情況下并不冒泡。
例如,除非我做一些具體的事情,否則這是行不通的
- <my-textarea-wrapper @focus="showFocus">
除非我們在包裝組件中編寫發出focus事件,否則將永遠不會調用showFocus事件處理程序。不過,Vue 確實為我們提供了一種以編程方式訪問應用于組件的偵聽器的方法,因此我們可以將它們分配到正確的位置:$listener對象。
再一想,原因很明顯:這允許我們將偵聽器傳遞到組件中的正確位置。例如,使用文本區域包裝器組件
- <div class="my-textarea-wrapper"> <textarea v-on="$listeners" ></textarea></div>
現在發生在textarea上的事件就是那些被傳遞的事件。
怎么理解vue中$listeners屬性?
假設有父組件Parent和子組件Child

那么你在使用Child時,傳入的所有v-on事件都可以在$listeners對象中找到。
- // Childcreated () { console.log(this.$listeners) // { 'event-one': f(), 'event-two': f() }}
為正確的元素分配屬性
如何處理textarea的rows或在任何元素上添加簡單工具提示的title標記等屬性呢
默認情況下,Vue采用應用于組件的屬性,并將其放在該組件的根元素上。但這并非總是我們想要的。如果我們從上方再次查看textarea包裝器,則在這種情況下,將屬性應用于textarea本身而不是div更有意義。
為此,我們告訴組件默認情況下不要應用屬性,而是直接使用$attrs對象應用它們,在 JS 代碼中:
- export default { inheritAttrs: false,}
在模板中
- <div class="my-textarea-wrapper"> <textarea v-bind="$attrs"></textarea></div>
官方文檔講解組件的屬性傳遞時,講到$attrs和inheritAttrs這兩個屬性,且兩個屬性結合會比較好用。乍一看沒看懂,結合代碼演示才搞清楚。

先隱藏 inheritAttrs: false和v-bind="$attrs",瀏覽器得到的結果如下

此處有兩處異常:
- 父組件中設置的placeholder等屬性沒有傳到子組件;
- 父組件設置的屬性傳遞給了子組件的根元素,即label,而label是不需要的
$attrs和inheritAttrs:false即分別用來解決這兩個問題首先,inheritAttrs:false解決子組件的根元素繼承父元素的屬性;其次,子組件中添加了v−bind="$attrs"的元素會繼承父組件的屬性,即使props中沒有定義該屬性
接受瀏覽器的鍵盤導航規范
可訪問性和鍵盤導航是Web開發中最常被遺忘的部分之一,也是編寫要在生態系統中正常運行的組件時要正確處理的最重要的事情之一。
這意味著要確保組件符合瀏覽器規范:tab鍵應該允許選擇表單字段。Enter通常用于激活按鈕或鏈接。
有關常見組件的鍵盤導航建議的完整列表,可以在W3C網站上找到。遵循這些建議將使您的組件可以在所有應用程序中使用,而不僅僅是那些與可訪問性無關的組件。
使用事件優先于回調
當涉及到從組件到其父組件的數據通信和用戶交互時,有兩個常見的選擇:props中的回調函數和事件。因為 Vue 的自定義事件不會像原生瀏覽器事件那樣冒泡,所以兩者在功能上是等效的,但是對于可重用的組件,建議能使用事件就使用事件,其次在再是回調,為什么?
在Fullstack電臺的一期節目中,Vue 核心團隊成員Chris Fritz給出了以下理由:
- 使用事件使父組件可以清楚地知道什么。它明確區分了“我們從父組件那里得到的東西”和“我們發送給組件的東西”。
- 可以在事件處理程序中直接使用表達式,從而為簡單情況提供極其緊湊的事件處理程序。
- 它更符合習慣——Vue示例和文檔傾向于使用事件來實現組件與其父組件之間的通信。
幸運的是,如果當前使用的是props回調,則很容易修改組件以發出事件。使用回調的組件可類似如下:

外面調用方式:
- <my-custom-component :onActionHappened="actionHandler" />
更改為基于事件的方法如下所示:

外面調用方式:
- <my-custom-component @action-happened="actionHandler" />
限制組件樣式
Vue的單文件組件結構使我們可以將樣式直接嵌入到組件中,尤其是當與作用域結合使用時,這為我們提供了一種很好的方式來發布完全打包的樣式化組件,而不會影響應用程序的其他部分。
由于該系統的強大功能,很容易將所有組件樣式放入組件中,并交付一個完全樣式化的組件。問題是:沒有任何應用程序的樣式是相同的,而使組件在我們的應用程序中看起來很完美的東西將使它在其他人的應用程序中脫穎而出。由于組件樣式通常比全局樣式表包含的時間晚,因此覆蓋它可能成為一場專一性的噩夢。
防止這種情況,建議任何CSS不是結構所必需的組件(顏色、邊框、陰影等)應該被排除在我們的組件文件本身或能夠被關閉。相反,考慮維護一個可定制的SCSS部分允許用戶定制他們的心的內容。

在 JS 中:

然后,我們可以

這將允許我們隨意使用現成的樣式,但是想要自定義的用戶不再需要創建高度專一覆蓋,他們只需通過將disableStyles屬性設置為true即可關閉樣式。