再好比,下拉列表、時間選擇器或者自動填充屬性等自定義控件都是很是複雜的,須要考慮不少邊緣的複雜狀況。雖然有不少庫很好的解決了這種複雜性,可是他們也帶來了很差的缺點,就是這類組件沒法自定義樣式。css
就拿下面的標籤輸入控件舉例:html
這個組件擁有一些有趣的功能:vue
若是你的項目中須要使用這樣一個組件,把這個做爲一個庫引入,而且剝離這些邏輯確定可以節省不少時間和精力。react
可是假如這個時候你須要一個不一樣樣式展示呢?git
下面這個組件擁有和上面組件同樣的行爲功能,可是佈局明顯不同:github
經過組合css和配置選項,你能夠嘗試在一個組件裏面都支持這些佈局,但顯然這不是很好的方法,萬一有一天你又須要另一個佈局,你又得去改這個組件,破壞了組件的封閉性,容易引發其它問題。ajax
針對以上狀況,咱們來介紹本文最重要的一個知識點。json
在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個類型的數據之一是最有用的。
最簡單的slot prop類型就是數據類型:strings,numbers,boolean values,arrays,objects等。
在咱們的links-list組件例子中,link
就是一個data prop類型的例子,它是一個擁有一些屬性的對象。
動做屬性是由子組件提供的一個函數,父組件能夠經過調用這個函數來觸發子組件裏面某些行爲。
舉個例子,咱們能夠給父組件傳遞一個bookmark
方法,這個方法用來爲給定連接添加書籤。
Bindings是一系列屬性或者監聽事件的集合,經過使用v-bind
或者v-on
,綁定到特定的元素中。
當你想要封裝有關如何與給定的元素進行交互的細節時,這些很是有用。
舉個例子,咱們提供了bookmarkButtonAttrs
綁定和bookmarkButtonEvents
綁定用來把這些細節移動到組件自身,而不是讓消費組件本身經過v-show
指令和@click
處理添加至書籤的按鈕邏輯。
名稱解釋: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
數據屬性:value
的newTag
的綁定屬性@keydown.enter
用來添加標籤和綁定@input
用來更新標籤的事件綁定屬性在咱們的當前佈局中,用戶經過在輸入元素中輸入以及敲擊enter鍵來完成添加一個新標籤的操做。但這也很容易想到,有些用戶但願可以提供點擊添加按鈕來添加標籤。
要實現這個很簡單,咱們只須要給slot scope傳遞一個addTag
的方法的引用。
消費者只須要解構出它們實際須要的屬性便可,因此若是你提供了它們可能用不到的屬性,它們也沒有什麼成本。
這就是到目前爲止咱們創建的函數式組件:
這個實際組件不包含任何html文本,而且咱們定義模板的父組件不包含任何行爲,是否是接近完美?如今咱們已經有了一個標籤輸入控件的函數式組件,咱們能夠很容易的編寫咱們想要的任何Html並將提供的插槽屬性應用到正確的位置來輕鬆實現替代佈局。
如下就是咱們利用咱們新的函數式組件從頭開始實現的堆疊式佈局。
看到這麼多的例子,你可能會想:「哇,當我須要另外一種形式的標籤組件時,每次我都須要寫這麼多html」,是的,你說的對。
不管何時你須要一個標籤輸入組件,你確實須要寫不少。
而不是這個,咱們一開始常規的寫法那樣:這有一個容易的解決方法:建立一個本身的包裹式組件!
這就是根據函數式組件編寫原始<tags-input>
組件的樣子
如今你可以只須要一行代碼就可以在任何你想要的地方使用這個組件。
一旦你意識到一個組件能夠不用渲染任何內容而只負責提供數據,那麼經過組件建模的行爲就沒了限制。
舉個列子,這裏有一個使用URL做爲屬性,從這個URL獲取json數據並給父組件傳遞響應數據的fetch-data
組件:
將一個組件拆分紅一個視圖組件和一個函數式組件是很是有用的一種模式,可使代碼複用更容易,但並非每次都值得這樣作。
若是有如下這類狀況,能夠考慮使用這種模式:
若是你正在研究一個在任何狀況看來都類似的組件,那就不要走這條路了。這種情形下將全部你須要的寫在一個組件裏面可能會更好更簡單。
此外,視圖代碼和業務邏輯分離只是一種下降代碼耦合,進而增長代碼健壯性的一種手段,其深層次的就是組件應該符合高內聚、低耦合的思想,其它符合這種思想的手段還有像控制反轉(IOC)、發佈訂閱模式等等,我以爲代碼越日後寫越應該培養這種意識,不然簡簡單單的寫寫業務代碼,以完成需求而寫代碼,提高進步會比較慢。
文章翻譯沒有徹底按照原文來翻譯,爲了可以更好理解我加了一些本身的理解,若是你喜歡個人文章,請點個贊表示鼓勵或者分享給你的朋友。