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

你是否曾經遇到過這樣一個場景,你有個需求須要引入一個第三方庫,然而你只須要使用這個庫裏面某一個功能,若是這個庫不支持分模塊導出的話,就會由於引入整個庫而致使項目體積變大,進而影響項目加載性能。

再好比,下拉列表、時間選擇器或者自動填充屬性等自定義控件都是很是複雜的,須要考慮不少邊緣的複雜狀況。雖然有不少庫很好的解決了這種複雜性,可是他們也帶來了很差的缺點,就是這類組件沒法自定義樣式。css

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

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

  • 不容許你添加劇復的標籤
  • 不容許添加空標籤
  • 自動去除標籤內容兩邊的空格
  • 點擊Enter鍵保存標籤
  • 點擊x字符刪除標籤

若是你的項目中須要使用這樣一個組件,把這個做爲一個庫引入,而且剝離這些邏輯確定可以節省不少時間和精力。react

可是假如這個時候你須要一個不一樣樣式展示呢?git

下面這個組件擁有和上面組件同樣的行爲功能,可是佈局明顯不同:github

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

經過組合css和配置選項,你能夠嘗試在一個組件裏面都支持這些佈局,但顯然這不是很好的方法,萬一有一天你又須要另一個佈局,你又得去改這個組件,破壞了組件的封閉性,容易引發其它問題。ajax

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

做用域插槽(Scoped Slots)

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

Scoped slots就像常規插槽同樣,可是它可以將參數從子組件傳遞到父組件/消費者。

常規slots就像是給組件傳遞了一段html文本,scoped slots就像是給組件傳遞了一個可以接收數據並返回Html的回調函數。less

經過向子組件裏面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控制反轉

相關文章
相關標籤/搜索