面試官:談談你理解的Vue無渲染組件?javascript
本身先想一分鐘。css
譯者注:英語和文筆有限,不對之處歡迎留言斧正!原文地址:css-tricks.com/building-re…html
網上有句話這樣來形容Vue,說 「Vue 是 React 和 Angular 的產物」。老實說,我也一直有這種感受。憑藉着較低的學習曲線,廣受開發人員的青睞和喜好。正是因爲Vue提供給開發者自由開放式的組件開發的能力,纔有了我今天這篇文章。vue
術語無渲染組件意指不渲染任何內容的組件。本文,咱們將介紹Vue是如何處理組件渲染工做的。java
咱們還將會看到如何使用 render()
函數來構建無渲染組件的。webpack
在閱讀本文以前假設你對Vue有必定的瞭解。若是你是一個新手請先閱讀Sarah Drasner's post。官方文檔也是不錯的資源。git
Vue提供了不少方法來渲染組件:github
template
屬性。容許咱們使用 JavaScript 的模板字符串來定義組件el
屬性。告訴Vue查詢DOM以獲取用做組件模板你可能據說過(可能很討厭):歸根結底,Vue和它全部的組件都只是JavaScript。我能理解你爲何以爲咱們編寫的HTML和CSS的數量是不對的了。用一個案例來闡明這一點:單文件組件。web
使用單文件組件,咱們能夠像這樣來定義Vue組件:面試
<template>
<div class="mood">
{{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }}
</div>
</template>
<script> export default { data: () => ({ todayIsSunny: true }) } </script>
<style> .mood:after { content: '🎉🎉'; } </style>
複製代碼
經過看上面官方提供的單文件標準格式,咱們怎麼能說Vue「只有JavaScript」 呢?可是,它確實就是。從表象來看,Vue只是想讓咱們便於管理咱們的頁面,樣式和其餘資源,而構建,編譯等工做交給了第三方工具,好比 webpack。
webpack 在檢索到 .vue
文件時,將進入編譯階段。期間,CSS會被提取到單獨的文件,剩下的內容將編譯成 JavaScript。相似下面的代碼:
export default {
template: ` <div class="mood"> {{ todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' }} </div>`,
data: () => ({ todayIsSunny: true })
}
複製代碼
額... 其實不徹底像上面的代碼。要了解接下來會發生什麼,咱們須要先聊聊模板編譯器。
對於編譯和運行Vue當前實現的每一個優化技術來講,Vue組件的構建過程這一步是必需的。
當模板編譯器遇到下面的代碼:
{
template: `<div class="mood">...</div>`,
data: () => ({ todayIsSunny: true })
}
複製代碼
首先它會提取模板屬性並將其內容編譯成 JavaScript,而後將渲染函數添加到組件對象上。反過來講就是,渲染函數會返回從模板屬性內容轉換成 JavaScript 後的內容。
這就是上面的模板在渲染函數中的樣子:
...
render(h) {
return h(
'div',
{ class: 'mood' },
this.todayIsSunny ? 'Makes me happy' : 'Eh! Doesn't bother me' ) } ... 複製代碼
有關渲染函數的更多內容,請參閱官方文檔。
如今,當組件對象傳遞給Vue時,組件對象中的渲染函數會通過一些優化處理後變成一個 VNode(虛擬節點)。VNode會被snabbdom(Vue內部用來管理虛擬DOM的庫)接手作進一步的處理。Sarah Drasner 解釋了上面渲染函數中的 h
函數。
VNode 是Vue渲染組件的一種方式。順便說一下,Vue還容許咱們在渲染函數中使用 JSX 語法。
咱們沒必要等Vue幫咱們添加渲染函數 — 咱們能夠本身定義渲染函數,並且它的優先級是高於 el
和 template
屬性的。移步這裏去瞭解渲染函數及其選項。
使用 Vue CLI 或其餘自定義腳手架工具構建Vue組件,你不用去考慮導入可能會影響構建文件大小的模板編譯器。你的組件都是通過了預處理,壓縮,優化等。在性能,文件體積方面都有出色的表現。
就像我說的,術語無渲染組件意指不渲染任何東西的組件。爲何咱們想要一個無渲染的組件呢?
咱們能夠經過無渲染組件建立一個抽象組件。就像 Java 中的抽象類,它自己不會作一些事情,只是提供一些接口方法等讓外部使用。後續咱們能夠經過不斷拓展該組件來實現更好更強大的組件。這也是正是 S.O.L.I.D 。
根據 S.O.L.I.D 的單一責任原則:
一個類應該只作一件事兒
咱們能夠把這種概念移植到Vue開發中,使每一個組件只作一件事兒。
你可能會像Nicky同樣,「是的,我知道。「好的,固然!」 你的組件可能會有一個名叫「password-input」,它確定會呈現一個密碼輸入框。問題在於,當你想要在其餘項目重用此組件時,你可能不得不查看組件源碼修改樣式或者HTML,以便能和新項目的樣式或設計圖保持統一。
若是你這樣作就破壞了 S.O.L.I.D 原則。也就是開閉原則:
類或者組件,應該對拓展開放,對修改關閉。
意思是,你應該拓展它,而不是修改組件的源代碼。
因爲Vue瞭解S.O.L.I.D原則,因此它容許組件具備 props、events、slots、以及 scoped slots ,從而使組件的通訊和拓展變得垂手可得。而後,咱們能夠構建具備全部功能的組件,而無需任何樣式或HTML。對於編寫可重用性和高性能的代碼來講真的很不錯。
這很簡單,不須要Vue CLI。
開關組件可讓你在開和關之間切換。而且它還提供了一些幫助方法供你使用。它對於構建組件(例如,開/關組件、自定義複選框和任何須要開/關狀態的組件)很是有用。
先找到咱們的組件:前往 CodePen的 JavaScript 部分,而後繼續。
// toggle.js
const toggle = {
props: {
on: { type: Boolean, default: false }
},
render() {
return []
},
data() {
return { currentState: this.on }
},
methods: {
setOn() {
this.currentState = true
},
setOff() {
this.currentState = false
},
toggle() {
this.currentState = !this.currentState
}
}
}
複製代碼
目前組件代碼仍是比較少的,功能還沒有完成。它須要一個模板,由於咱們不但願這個組件展現任何東西,因此咱們必須確保它能適用於任何組件。
暗示插槽!
插槽容許咱們在標籤體中放置內容。像這樣:
<toggle>
This entire area is a slot.
</toggle>
複製代碼
在Vue單文件組件中,咱們能夠這樣來定義一個插槽:
<template>
<div>
<slot/>
</div>
</template>
複製代碼
好了,在開關組件的 render()
函數中咱們能夠這樣來作:
// toggle.js
render() {
return this.$slots.default
}
複製代碼
在開關組件裏咱們能夠自由的放置東西了。
在 toggle.js
中,methods
方法對象上有一些控制開關狀態的方法和一些輔助方法。若是咱們能讓開發者調用他們那就太好了。而咱們目前正在使用的插槽沒法作到這一點,由於它不容許咱們公開組件中的任何內容。
咱們想要的實際上是 scoped slots。做用域插槽的工做方式跟插槽同樣。但對比插槽的優勢在於,具備做用域插槽的組件在不觸發事件的狀況下也能夠暴露數據。看下面的代碼:
<toggle>
<div slot-scope="{ on }">
{{ on ? 'On' : 'Off' }}
</div>
</toggle>
複製代碼
div
上的 slot-scope
屬性經過對象解構並獲取從開關組件透傳過來的數據。
回到 render()
函數,咱們這樣作:
render() {
return this.$scopedSlots.default({})
}
複製代碼
此次,咱們將 $scopedSlots
對象上的 default 屬性做爲方法調用。由於做用域插槽是帶有一個參數的方法。這種狀況下,方法名是默認的,由於咱們沒有提供具名插槽,因此它將做爲惟一存在的做用域插槽。而後咱們就能夠把組件中的方法以做用域插槽參數的形式暴露出去了。在這個栗子中,讓咱們暴露 on
的當前狀態和操做該狀態的一些方法吧:
render() {
return this.$scopedSlots.default({
on: this.currentState,
setOn: this.setOn,
setOff: this.setOff,
toggle: this.toggle,
})
}
複製代碼
咱們作的這些操做都在 Codepen 上,下面是截圖:
下面是相關的HTML代碼:
<div id="app">
<toggle>
<div slot-scope="{ on, setOn, setOff }" class="container">
<button @click="click(setOn)" class="button">Blue pill</button>
<button @click="click(setOff)" class="button isRed">Red pill</button>
<div v-if="buttonPressed" class="message">
<span v-if="on">It's all a dream, go back to sleep.</span>
<span v-else>I don't know how far the rabbit hole goes, I'm not a rabbit, neither do I measure holes.</span>
</div>
</div>
</toggle>
</div>
複製代碼
click()
方法其實就是代理開關組件內的真正要執行的方法。你能夠查看下面的 click 方法:new Vue({
el: '#app',
components: { toggle },
data: {
buttonPressed: false,
},
methods: {
click(fn) {
this.buttonPressed = true
fn()
},
},
})
複製代碼
咱們仍然能夠從 Toggle 組件傳遞 props 和觸發事件。做用域插槽不會受到任何影響。
這是一個基礎的示例,但咱們能夠看到,當咱們開始構建日期選擇器或自動完成提示等組件時,這種作法是很是適用且強大的。咱們能夠在多個項目重複使用這些組件,而沒必要擔憂那些討厭的樣式妨礙咱們。
咱們能夠作的另外一件事是從做用域插槽中公開可訪問的屬性而沒必要擔憂拓展組件後的訪問性問題。
el
,template
或者單文件組件都會被編譯成渲染函數最後,下面是我維護的一個Q羣,喜歡Vue的同窗,歡迎掃碼進羣哦,讓咱們一塊兒交流學習吧。也能夠加我我的微信:G911214255 ,備註 掘金
便可。