(譯)Vue.js 構建一個"無渲染"元件

gongph發表於2019-06-28

面試官:談談你理解的Vue無渲染元件?

自己先想一分鐘。

(譯)Vue.js 構建一個"無渲染"元件

譯者注:英語和文筆有限,不對之處歡迎留言斧正!原文地址:css-tricks.com/building-re…

網上有句話這樣來形容Vue,說 “Vue 是 React 和 Angular 的產物”。老實說,我也一直有這種感覺。憑藉著較低的學習曲線,廣受開發人員的青睞和喜愛。正是由於Vue提供給開發者自由開放式的元件開發的能力,才有了我今天這篇文章。

術語無渲染元件意指不渲染任何內容的元件。本文,我們將介紹Vue是如何處理元件渲染工作的。

我們還將會看到如何使用 render() 函式來構建無渲染元件的。

在閱讀本文之前假設你對Vue有一定的瞭解。如果你是一個新手請先閱讀Sarah Drasner's post官方文件也是不錯的資源。

解開Vue如何渲染元件的神祕面紗

Vue提供了很多方法來渲染元件:

  • 單檔案元件。讓我們像寫普通HTML檔案一樣去定義元件
  • Vue 提供的 template 屬性。允許我們使用 JavaScript 的模板字串來定義元件
  • Vue 提供的 el 屬性。告訴Vue查詢DOM以獲取用作元件模板

你可能聽說過(可能很討厭):歸根結底,Vue和它所有的元件都只是JavaScript。我能理解你為什麼覺得我們編寫的HTML和CSS的數量是不對的了。用一個案例來闡明這一點:單檔案元件。

使用單檔案元件,我們可以像這樣來定義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: '&#x1f389;&#x1f389;';
  }
</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幫我們新增渲染函式 — 我們可以自己定義渲染函式,而且它的優先順序是高於 eltemplate 屬性的。移步這裡去瞭解渲染函式及其選項

使用 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。對於編寫可重用性和高效能的程式碼來說真的很不錯。

構建一個 “Toggle” 無渲染元件

這很簡單,不需要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
}
複製程式碼

在開關元件裡我們可以自由的放置東西了。

使用 Scoped Slots 向外部傳送資料

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,
  })
}
複製程式碼

使用Toggle元件

我們做的這些操作都在 Codepen 上,下面是截圖:

(譯)Vue.js 構建一個"無渲染"元件

下面是相關的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>
複製程式碼
  1. 首先我們從作用於插槽中解構出開關狀態和一些輔助方法
  2. 然後,我們在作用域插槽內建立了兩個按鈕,一個用於切換當前狀態,另一個用於關閉。
  3. 在顯示結果之前,click() 方法其實就是代理開關元件內的真正要執行的方法。你可以檢視下面的 click 方法:
new Vue({
  el: '#app',
  components: { toggle },
  data: {
    buttonPressed: false,
  },
  methods: {
    click(fn) {
      this.buttonPressed = true
      fn()
    },
  },
})
複製程式碼

我們仍然可以從 Toggle 元件傳遞 props 和觸發事件。作用域插槽不會受到任何影響。

這是一個基礎的示例,但我們可以看到,當我們開始構建日期選擇器或自動完成提示等元件時,這種做法是非常適用且強大的。我們可以在多個專案重複使用這些元件,而不必擔心那些討厭的樣式妨礙我們。

我們可以做的另一件事是從作用域插槽中公開可訪問的屬性而不必擔心擴充元件後的訪問性問題。

總結

  • 元件的渲染函式非常強大
  • 快速構建你的Vue元件
  • 元件的 eltemplate 或者單檔案元件都會被編譯成渲染函式
  • 嘗試構建更小的元件以獲得更多可重用的程式碼
  • 你的程式碼不一定是S.O.L.I.D,但務必準守一個好的編碼風格

參考資料

最後,下面是我維護的一個Q群,喜歡Vue的同學,歡迎掃碼進群哦,讓我們一起交流學習吧。也可以加我個人微信:G911214255 ,備註 掘金 即可。

Q1769617251

相關文章