1. 程式人生 > >(譯)函式式元件在Vue.js中的運用

(譯)函式式元件在Vue.js中的運用

你是否曾經遇到過這樣一個場景,你有個需求需要引入一個第三方庫,然而你只需要使用這個庫裡面某一個功能,如果這個庫不支援分模組匯出的話,就會因為引入整個庫而導致專案體積變大,進而影響專案載入效能。

再比如,下拉列表、時間選擇器或者自動填充屬性等自定義控制元件都是非常複雜的,需要考慮很多邊緣的複雜情況。雖然有很多庫很好的解決了這種複雜性,但是他們也帶來了不好的缺點,就是這類元件無法自定義樣式。

就拿下面的標籤輸入控制元件舉例:

這個元件擁有一些有趣的功能:

  • 不允許你新增重複的標籤
  • 不允許新增空標籤
  • 自動去除標籤內容兩邊的空格
  • 點選Enter鍵儲存標籤
  • 點選x字元刪除標籤

如果你的專案中需要使用這樣一個元件,把這個作為一個庫引入,並且剝離這些邏輯肯定能夠節省很多時間和精力。

但是假如這個時候你需要一個不同樣式展現呢?

下面這個元件擁有和上面元件一樣的行為功能,但是佈局明顯不一樣:

https://codepen.io/adamwathan/pen/KomKNK

通過組合css和配置選項,你可以嘗試在一個元件裡面都支援這些佈局,但顯然這不是很好的方法,萬一有一天你又需要另外一個佈局,你又得去改這個元件,破壞了元件的封閉性,容易引起其它問題。

針對以上情況,我們來介紹本文最重要的一個知識點。

作用域插槽(Scoped Slots)

在Vue.js中,slots是元件中的一個佔位符元素,會被從父元件/消費者中傳過來的內容替換。

Scoped slots就像常規插槽一樣,但是它能夠將引數從子元件傳遞到父元件/消費者。

常規slots就像是給元件傳遞了一段html文字,scoped slots就像是給元件傳遞了一個能夠接收資料並返回Html的回撥函式。

通過向子元件裡面slot元素增加props,將引數傳遞給父元件。父元件通過解構destructuring slot-scope裡面接收的屬性資料來獲得這些引數。

這裡有一個為每一個list元素暴露scoped slot屬性的LinksList元件,並且通過:link prop將每一項的資料傳遞迴給父元素。

通過將 :link prop 新增到LinksList元件中的slot元素,父元素元件現在能夠通過 slot-scope
訪問的到這些資料並且在自己的slot模組裡面使用它。

插槽屬性的型別

你可以傳遞任何型別給slot,但是我發現使用以下3個型別的資料之一是最有用的。

資料(Data)

最簡單的slot prop型別就是資料型別:strings,numbers,boolean values,arrays,objects等。

在我們的links-list元件例子中,link就是一個data prop型別的例子,它是一個擁有一些屬性的物件。

父元件能夠渲染這些資料或者自己決定該如何去渲染它

動作(Actions)

動作屬性是由子元件提供的一個函式,父元件可以通過呼叫這個函式來觸發子元件裡面某些行為。

舉個例子,我們可以給父元件傳遞一個bookmark方法,這個方法用來為給定連結新增書籤。

當用戶點選一個未新增至書籤的連結旁邊的按鈕時,父元件能夠呼叫這個操作。

繫結(Bindings)

Bindings是一系列屬性或者監聽事件的集合,通過使用v-bind或者v-on,繫結到特定的元素中。

當你想要封裝有關如何與給定的元素進行互動的細節時,這些非常有用。

舉個例子,我們提供了bookmarkButtonAttrs繫結和bookmarkButtonEvents繫結用來把這些細節移動到元件自身,而不是讓消費元件自己通過v-show指令和@click處理新增至書籤的按鈕邏輯。

程式碼如上,現在如果消費元件喜歡,它們可用運用這些繫結到bookmark按鈕上並且不用關心它們內部的實現。

Renderless Components

名稱解釋:Renderless Components,直譯為非渲染元件,我更喜歡叫函式式元件(借鑑於react中的叫法,以下統稱函式式元件)。

函式式元件是一個不渲染任何html文字的元件。

相反,它只管理狀態和行為,給父元件或者消費元件暴露一個作用域插槽,以便它們能自己控制該渲染的內容。

函式式元件能夠準確的渲染你給它傳入的內容,無需任何其它元素。

那為什麼這樣有用呢?

分離層現和行為

因為函式式元件只處理狀態和行為,它們不會做出任何有關設計和佈局的決定。

那就意味著如果你能找出一種方式將像我們的標籤輸入功能這樣有趣的行為從ui元件裡面剝離出來,你就能夠複用這個函式式元件去實現任何標籤輸入元件的佈局。

下面都是標籤輸入元件,但這次是由一個函式式元件支援。

那它是怎麼支援的呢?

函式式元件的基本結構

函式式元件僅僅暴露一個scoped slot,消費者可以在其中提供整個他們想要渲染的模組。

一個基本的函式式元件的骨架像下面這樣:

它沒有template標籤或者不渲染任何html文字,相反,它通過使用一個 render函式去呼叫能夠訪問所有的slot props的預設的作用域插槽,然後返回結果。

任何父元件/消費者都能夠在自己的模板中,通過解構slot-scope中的exampleProp去使用。

一個實際的使用案列

讓我們從頭開始構建一個標籤輸入控制元件的函式式版本。

我們首先要建立一個無插槽的空白的無渲染元件,

以及一個靜態的,沒有任何互動的父元件,然後將其傳遞到子元件的插槽中,

一步一步的,我們將會為函式式元件增加狀態和行為,同時通過 slot-scope暴露給我們佈局的地方來完善這個元件。

標籤列表

首先,我們將靜態列表替換為動態列表。

這個標籤輸入元件是自定義表單控制元件,和這個原始例子一樣,這個tags應該在父元件中,並且通過v-model繫結到元件中。

我們首先給函式式元件增加一個value屬性,並將其傳遞給一個名為tags的插槽。

接下來,在父元件中我們將會增加 v-model指令,從 slot-scope中獲取到tags,然後使 用v-for指令來遍歷它們。

這個tags slot屬性就是一個很好的資料屬性的例子

刪除標籤

下一步,當點選X按鈕,刪除一個標籤。

在函式式元件中,我們將會增加一個removeTag的方法,並且將其作為一個slot屬性傳遞給父元素。

然後我們在父元件的按鈕元素中增加一個 @click事件,這個事件能夠在當前的標籤中呼叫 removeTag方法。

這個removeTag slot屬性就是一個動作屬性的例子

點選回車鍵新增標籤

新增新標籤比前面兩個例子都要複雜些。

為了理解為什麼,我們先來看一下傳統的元件都是怎麼實現的。

我們在newTag屬性中保持跟蹤這個新標籤(在被新增之前),然後我們通過 v-model將這個屬性繫結到input中。

一旦使用者點選enter鍵,只要這個標籤是合法的,我們就把它新增到list陣列中,然後清除input輸入的值。

這兒的問題就是我們怎樣通過scoped-slot傳遞v-model繫結。

好吧,如果你深入瞭解過Vue,你應該知道v-model其實就是一個語法糖,它負責將value特性繫結到一個名叫value的prop上,同時在其input事件被觸發時,將新的值通過自定義的input事件丟擲。

這就意味著我們可以在函式式元件中做一些更改來處理這個新增的行為:

  • 元件中新增一個newTag資料屬性
  • 回傳一個繫結到:valuenewTag的繫結屬性
  • 回傳一個繫結@keydown.enter用來新增標籤和繫結@input用來更新標籤的事件繫結屬性

現在我們只需要在父元件的input元素中繫結這些屬性即可;

明確新增新標籤

在我們的當前佈局中,使用者通過在輸入元素中輸入以及敲擊enter鍵來完成新增一個新標籤的操作。但這也很容易想到,有些使用者希望能夠提供點選新增按鈕來新增標籤。

要實現這個很簡單,我們只需要給slot scope傳遞一個addTag的方法的引用。

當設計像這樣的函式式元件的時候,最好是多提供一些slot props,總比少要好。

消費者只需要解構出它們實際需要的屬性即可,所以如果你提供了它們可能用不到的屬性,它們也沒有什麼成本。

執行Demo

這就是到目前為止我們建立的函式式元件:

這個實際元件不包含任何html文字,並且我們定義模板的父元件不包含任何行為,是不是接近完美?

換個佈局

現在我們已經有了一個標籤輸入控制元件的函式式元件,我們可以很容易的編寫我們想要的任何Html並將提供的插槽屬性應用到正確的位置來輕鬆實現替代佈局。

以下就是我們利用我們新的函式式元件從頭開始實現的堆疊式佈局。

建立自己的包裹式元件

看到這麼多的例子,你可能會想:“哇,當我需要另一種形式的標籤元件時,每次我都需要寫這麼多html”,是的,你說的對。

無論什麼時候你需要一個標籤輸入元件,你確實需要寫很多。

而不是這個,我們一開始常規的寫法那樣:

這有一個容易的解決方法:建立一個自己的包裹式元件!

這就是根據函式式元件編寫原始 <tags-input>元件的樣子

現在你能夠只需要一行程式碼就能夠在任何你想要的地方使用這個元件。

更加瘋狂的是

一旦你意識到一個元件可以不用渲染任何內容而只負責提供資料,那麼通過元件建模的行為就沒了限制。

舉個列子,這裡有一個使用URL作為屬性,從這個URL獲取json資料並給父元件傳遞響應資料的fetch-data元件:

這是傳送ajax請求最好的方法麼?可能不是,但它真的很有趣!

結論

將一個元件拆分成一個檢視元件和一個函式式元件是非常有用的一種模式,可以使程式碼複用更容易,但並不是每次都值得這樣做。

如果有以下這類情況,可以考慮使用這種模式:

  • 你打算構建一個庫,並且希望使用者可以自定義元件的外觀
  • 在你的專案中有很多功能相似但佈局不一樣的元件

如果你正在研究一個在任何情況看來都相似的元件,那就不要走這條路了。這種情形下將所有你需要的寫在一個元件裡面可能會更好更簡單。


此外,檢視程式碼和業務邏輯分離只是一種降低程式碼耦合,進而增加程式碼健壯性的一種手段,其深層次的就是元件應該符合高內聚、低耦合的思想,其它符合這種思想的手段還有像控制反轉(IOC)、釋出訂閱模式等等,我覺得程式碼越往後寫越應該培養這種意識,否則簡簡單單的寫寫業務程式碼,以完成需求而寫程式碼,提升進步會比較慢。

相關連結

Demo原始碼(Vue單檔案元件形式)

原文連結

IOC控制反轉