iT邦幫忙

2024 iThome 鐵人賽

DAY 1
7
Modern Web

為你自己寫 Vue Component系列 第 1

[為你自己寫 Vue Component] 簡介

  • 分享至 

  • xImage
  •  

[為你自己寫 Vue Component] 簡介

身為前端工程師,元件管理是不論選用哪一個前端框架的開發人員都需要面臨的挑戰。能不能妥善規劃與管理元件,很大程度上決定了專案未來的走向。好的元件規劃可以讓開發更有效率,反之,如果沒有妥善應用元件或是濫用元件,可能都會是一場萬劫不復的災難。

在面對全新專案時,你會選擇使用 UI Library 嗎?你要怎麼知道你選擇的、你擅長的 UI Library 是不是適合你的專案呢?

如果能有一套 UI Library 完全貼合專案需求,那就太好了。但現實總是殘酷的,幾乎沒有任何一個專案可以完全依賴某一套 UI Library 來滿足需求。更多時候,我們需要使勁地「魔改」,利用各種權重的 CSS 覆蓋 UI Library 原有的樣式,甚至用盡各種曲折離奇的黑魔法來讓 UI Library 變成我們想要的樣子。改到最後開始懷疑人生,想著自己寫可能還比較快,但頭已經洗下去了,只能摸摸鼻子繼續施展魔法。然後,那天 UI Library 更新時,我們又得重新來過。

使用 UI Library 確實可以幫助我們快速完成專案,但站在長期維護的角度,我們需要思考的是:我們是在使用 UI Library,還是被 UI Library 使用?

我們來嘗試維護一套自己的 UI Library 吧!

也許你不一定認同我的看法,但我認為,身為一個優秀的前端工程師,即使沒有自己手刻一套 UI Library 的打算,也應該要了解每個元件應該怎麼實現,應該涵蓋哪些功能,有哪些細節是我們需要特別留意的。花點時間深入理解每個元件的運作原理,對於如何善用 UI Library 也會有更深刻的體悟。

那麼,是不是從此就捨棄 UI Library 呢?倒也不是,UI Library 仍然是我們的好夥伴,但我們需要知道如何與它合作,而不是被它束縛。在我的習慣中,UI Library 更像是一本又一本的參考書,透過這些參考書,我們可以了解到一個看似簡單的元件背後隱藏了多少細節,也可以學習到更多前端技巧與資料管理的方法。

在開始之前,建立最基本的 Mindset

說了這麼多,來談談我會如何撰寫「為你自己寫 Vue Component」這個系列文章吧。

這是一個針對 Vue 所設計的 UI Library 分享,是我真正會套用在專案中使用的元件。除了使用 Vue 3 之外,有以下幾個名詞在開始前能夠先有些概念會更好:

Composition API

我相信 Composition API 已經不是什麼新鮮的名詞了,所以不需要多作解釋。在這個系列文章中,將會完全使用 Composition API 來撰寫元件。

TypeScript

TypeScript 是 JavaScript 的超集,這表示任何合法的 JavaScript 程式碼在 TypeScript 中也是有效程式碼。Typescript 為 JavaScript 引入了型別系統,除了能進行型別檢查外,也讓我們實作出來的元件有更清楚的「程式碼提示」。

TypeScript 搭配 Volar 的型別提示

在這個系列文章中,我們將使用 TypeScript 來定義元件的型別。這不僅能幫助我們透過型別檢查排除潛在的低級錯誤,還能透過明確的型別提示,讓與我們合作的開發人員即使不深入了解實作細節,也能輕鬆掌握如何正確使用這些元件。

原子化設計(Atomic Design)

原子設計是一種網頁設計方法,由 Brad Frost 在 2013 年時發表的文章中提出。這個方法將網頁設計分為五個不同的層級:

Atomic Design

  • 原子(Atoms):為網頁構成的基本元素,像是按鈕、標籤、輸入框等。
  • 分子(Molecules):由一個或多個元素構成的簡單 UI 物件,像是搜尋框,由按鈕、標籤、輸入框組成。
  • 組織(Organisms):由原子與分子組成的元素,像是導覽列。
  • 模板(Templates):由原子、分子與有機體組成的元素,像是頁面的版型。
  • 頁面(Pages):由原子、分子、有機體與模板組成的元素,像是完整的網頁。

在我的開發經驗中,我沿用了這五個層級,並且重新定義了每個層級的權責與劃分標準,可能跟 Brad Frost 的定義會有一點不同,但我想核心精神是相似的。

  • 原子(Atoms)

    元件的最小單位,如:<AtomicButton><AtomicBreadcrumb><AtomicPagination>,在這個分類中的元件具有最高的重用性,甚至只要調整 CSS 的設定就可以跨專案使用。因此,這裡的元件可以貼近但不專為特定需求服務。

  • 分子(Molecules)

    可能是由原子組成的元件,更多時候是與特定專案高度耦合的元件,牽涉到專案獨有的業務邏輯。儘管如此,元件仍然可以在專案中被重複使用,因此我們需要盡可能不讓這裡的元件與後端 API 或資料模組耦合。

  • 組織(Organisms)

    不僅僅是與專案耦合,更可能與特定的模板、頁面高度整合。與分子的主要區別是,這裡的元件會直接與後端 API 或資料模組有所耦合,因此這裡的元件更難在專案中被重複使用。在這裡的元件不再是為了重用而存在,主要目的反而是將複雜邏輯隔離開來。

  • 模板(Templates)

    在開發習慣上,我通常會直接進入頁面(Pages),但如果遇到高度重複的頁面結構,模板層就會起到很大的作用。在設計上,可能會從頁面層取得資料,再傳入模板層,這樣可以讓我們更容易重複使用模板。

  • 頁面(Pages)

    頁面是最好劃分的部分,在 Vue 專案中,它就是 Route Component。這裡的元件是最高層的元件,負責整合所有的元件,並且與後端 API 進行溝通。這裡的元件完全不考慮重用,因為它是最高層的元件,目的是將所有元件整合在一起,並且與後端 API 進行溝通。

這個系列文章的內容只會聚焦在原子(Atoms)這個層級,因此在設計時會盡可能將元件設計得更加通用,這樣可以讓我們在未來的專案中更容易重複使用這些元件。

BEM

隨著 Tailwind CSS 與 UnoCSS 的興起,BEM 這個名詞可能已經不再那麼熱門了。但我個人認為 BEM 仍然是一個很好的命名規則,這個系列文章中會使用 BEM 的規範來命名元件。

BEM 是由 Block(區塊)、Element(元素)與 Modifier(修飾符)組合而成。這個命名規則是由 Yandex 的一群前端工程師所提出的,目的是讓 CSS 的命名更有意義、更有邏輯,也更容易維護。

在命名上,會由下列兩種符號來區分:

  • __ 雙底線:用來區分 Element,表示 Block 內的元素。
  • -- 雙減號:用來區分 Modifier,表示 Block 或 Element 的狀態。

以按鈕為例,一個按鈕的命名可能會是這樣:

<button class="atomic-button atomic-button--primary atomic-button--small">
  <span class="atomic-button__prepend"></span>
  <span class="atomic-button__content">按鈕</span>
  <span class="atomic-button__append"></span>
</button>

這樣的命名方式可以讓我們相對清楚地知道這個元件是什麼,有哪些狀態,也有哪些元素組成。

在這個系列的內容中,每個元件都是一個獨立的 Block,而元件內的 Element 在本系列文章中會使用「區塊」表示,這是為了與 HTML 的元素有所區別。

SCSS

SCSS 是基於 CSS 語法的超集,它是 Sass(Syntactically Awesome Style Sheets)的一部分。SCSS 增加了許多強大的功能,如巢狀(Nesting)、變數(Variables)、混合(Mixins)、迴圈(Loops)等,使我們可以用更簡單的方式設定 CSS。

簡單的 SCSS 語法如下:

  • 變數(Variables):

    $button-height: 40px;
    

    SCSS 的變數與 CSS 的變數不同,記得不要搞混了,下面是 CSS 的變數:

    .atomic-button {
      --button-height: 40px;
    }
    
  • 混合(Mixins):

    @mixin sr-only {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border-width: 0;
    }
    
    .atomic-checkbox {
      &__input {
        @include sr-only;
      }
    }
    
  • 迴圈(Loops):

    .atomic-button {
      &--contained {
        @each $color, $value in $color-map {
          .atomic-button--#{$color} {
            background-color: rgba($value, 1);
          }
        }
      }
    }
    

這個系列文章中將使用 SCSS 來設定,不熟悉也沒關係,遇到不懂的地方再查就好了。

Tailwind CSS

Tailwind CSS 應該不會在接下來的內容中再次出現,但儘管使用了 BEM 來命名元件,更多時候我其實會搭配 Tailwind CSS 一起使用。以上面的範例為例,我可能會這樣寫:

.atomic-button {
  @apply inline-flex items-center justify-center;
  @apply h-[var(--button-height)];
  @apply text-sm text-center;
  @apply rounded-md;
}

搭配 SCSS 的字串插值(#{}),我們可以更方便地使用 Tailwind CSS 的 utility class,這樣可以更快地完成元件的撰寫,並且達到全站統一的效果。

但鑒於 Tailwind CSS 與 SCSS 的混合魔法對於不熟悉的人來說可能更難以理解,因此在這個系列文章中會以純 SCSS 來撰寫元件,但對於整個環境的 Reset 還是沿用 Tailwind CSS 的設定。

@tailwind base;

最後,感謝你願意點開連結開始閱讀這一系列的文章。這不是一個新手向的教學,對我而言,很多細節都是經過無數次修改、反覆驗證與腦力激盪後的結果。這些元件囊括了許多 UI Library 中我覺得優良的部分,出處不僅限於 Vue 這個框架。因此,如果你是 Vue 的初學者,我仍然推薦你跟著這個我們一起學習,你可能不會一次就上手,但它會是在你前端旅程中的一劑大補帖。

不論你是否是初學者,或是對元件設計非常有心得,不論你是寫 Vue 還是 React,我都希望「為你自己寫 Vue Component」這個系列文章能夠對你有所幫助。文章中有任何疑問或寫錯的地方,歡迎討論、指正。

感謝高見龍大大願意讓我使用「為你自己寫 Vue Component」這個名字,這絕對是整個系列文章中最難寫的部分了!


下一篇
[為你自己寫 Vue Component] AtomicLink
系列文
為你自己寫 Vue Component30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
MUKIwu
iT邦新手 5 級 ‧ 2024-10-10 19:50:52

原來 @apply 可以分段使用,不用全部寫在一起 XD,學到惹!

Alex Liu iT邦新手 4 級 ‧ 2024-10-10 20:13:37 檢舉

是的!可以分段使用,我會將相同類型的集中在一起,這樣看起來也比較舒服一點。

1
Rafael
iT邦新手 1 級 ‧ 2024-12-13 09:29:07

寫得很棒,觀念很適合新手(我)入門,最近一直被UI library套殺,沒辦法吻合客製需求XD。
想請教作者大大,如果不採取SCSS-BEM命名style,利用<style scoped> 讓元件樣式封裝起來,會有什麼缺點嗎,感恩🧡

Alex Liu iT邦新手 4 級 ‧ 2024-12-15 16:23:24 檢舉

感謝你!💚

我自己在專案開發時幾乎不會選擇 scoped,原因之一是 <style scoped> 的運作方式是使用 data-v-hash 來增加 CSS 的權重,如果沒有良好的規範的話還是有機率被污染的,例如:

.container .menu {
  font-size: 1.5rem;
}
.menu[data-v-hash] {
  color: red;
}

考量到這種情境,我個人覺得 BEM 還是比較能有效避免污染的問題,想要覆蓋元件樣式的話也會是在「有意識」的狀態下進行,也比較能減少不小心覆蓋到其他元件的樣式的情況。

希望有幫助到你。

Rafael iT邦新手 1 級 ‧ 2024-12-18 15:11:30 檢舉

謝謝大大🧡,最近參考Vuetify發現它們家,也是採SASS客製化變數,給開發者調整樣式。
用scoped封裝樣式發現明顯缺點,除了汙染外,元件樣式調動的參數開口會放到props上,會有一坨props XD。

我要留言

立即登入留言