全新的 React 組件設計理念 Headless UI

前言

其實,最早接觸 Headless UI 是在去年,碰巧看到了一個非常前沿且優秀的組件庫 ---- Chakra UI,這個組件庫本身就是 Headless UI 的實踐者,同時也是 CSS-IN-JS 的集大成者。

我當時看過之後,就對該理念產生了很大的興趣,同時工作中也正好有機會實踐(着手公司開源組件庫大版本重構),因此對該理念也有一定的實踐經驗。

那麼今天,也是想和大家分享介紹下這項還算前沿的技術,另一方面是也算是個人的一份技術總結,這裏也希望感興趣的小夥伴可以在評論區探討。

契機:React Hooks 的誕生

React Hooks 可以說是 Headless UI 得以實現的基石,爲什麼這麼說,這裏我們首先聊聊 React Hooks。

React Hooks 是什麼

我們都知道,React Hooks 是在 V16.8 版本誕生了,是它讓我們的函數組件真正擁有了狀態。如下圖,我們以數字累加這個功能舉例,可以看到對於同樣的功能,React Hooks 的寫法相對於過去類組件的寫法從代碼上會減少一丟丟。

但僅僅是因爲如此才支持它嗎?

我們要知道,在 React v16.8 之前,一般情況下,普通的 UI 渲染直接使用函數組件就好,需要使用 state 或者其他副作用之類功能時,纔會使用類組件。

兩者分工也算合理,那麼 hooks 的誕生又是爲何?僅僅是爲函數組件賦能嗎?從使用者的角度來說,這顯然說不過去,徒增了學習成本不說,還多了一個糾結選項(函數組件 vs 類組件)。

React Hooks 的意義

所以,事情並沒有那麼簡單。我們可以推斷,對於 hooks 它肯定解決一些 “「類組件存在的不足或痛點”」 ,這裏就不賣關子,羅列 2 點:

  1. 狀態邏輯在組件之間難以複用

在過去,狀態邏輯的複用往往會採用高階組件來實現。但劣勢也非常明顯,需要**「在原來的組件外再包裹一層父容器。」** 導致層級冗餘,甚至嵌套地獄,引來了很多吐槽點:

相信使用 Redux 的同學都知道,爲了快速狀態管理到組件的注入,會使用 connect 對組件進行包裹,但是隨着項目迭代,打開 DevTools 查看時發現 DOM 往往臃腫不堪。

  1. 複雜組件變得難以理解和維護

複雜組件本身就很複雜,但是類組件讓其變得更賤難以理解和維護。比如:在一個生命週期函數中往往存在**「不相干的邏輯混雜」**在一起,或者**「一組相干的邏輯分散」**在不同的生命週期函數中,這裏分別舉個例子:

長期以往我們的代碼只會變得糟糕難懂。

React Hooks 對組件開發的影響

通過 React Hooks,我們可以把組件的狀態邏輯抽離成自定義 hooks,相干的邏輯放在一個 Hook 裏,不相干的拆分成不同的 hook,最終在組件需要時引入,實現狀態邏輯在不同組件之間複用。

正是因爲 React Hooks 的誕生,使 Headless UI 組件在技術上成爲可能,這也是它爲什麼最近纔開始流行的原因。

什麼是 HeadLess UI

Headless UI 的定義

Headless UI 目前社區還在探索實踐階段,這裏我對它做了個簡單定義:Headless UI 「一套基於 React Hooks 的組件開發設計理念,強調只負責組件的狀態及交互邏輯,而不管標籤和樣式。」 其本質思想其實就是關注點分離:將組件的 “狀態及交互邏輯” 和“UI 展示層”實現解耦。

Headless UI 組件

從實體上看,Headless UI 組件就是一個 React Hook。

從表象上來看,Headless UI 組件其實就是一個什麼也不渲染的組件。

爲什麼會有 Headless UI

那麼我們爲什麼需要一個啥也不渲染的組件呢?

這裏我們還是以數字加減這個功能舉例,先思考設計實現一個數字加減器 Counter 組件。

傳統版組件的設計痛點

按照傳統的模式,我們可能會直接去編寫導出一個名字叫 Counter 組件,然後使用上直接渲染它即可,對於組件的功能通過 props 設置,比如非受控初始數字值。

那麼這麼做有什麼滿足不了的痛點呢?我們這裏隨便舉個場景,然後分別來從**「組件的使用者、維護者以及服務的產品」**三個角度來分析下。

使用者 - 高定製業務場景如何實現滿足?

現在我們業務有這樣的訴求:左右兩個加減按鈕要求支持長按後懸浮展示 Tooltip 提示。

其實從產品角度這個需求很樸實,提升交互體驗嘛。但是如果按照之前傳統的組件設計,那就頭疼了。它一整個都是組件庫裏面暴露出來的(假設哈),怎麼去侵入到裏面給加減按鈕加 Tooltip 呢?

其實,對於組件這樣定製業務場景的訴求,我們一般解決思路可能是這樣:

隨着方案越往後選擇,我們的代價是越來越高的,臉上的痛苦面具也越來越明顯。

維護者 - 「組件」 「API」 「日趨複雜,功能擴展 & 向下兼容的苦惱?」

對於維護者而言,如果要去滿足這樣的訴求,那麼他可能會這麼做。

一開始,需求比較簡單,我們可以通過新增 API 動態注入要實現的功能,對於上面的訴求,我們可能會新增 xxxButtonTooltipText 之類的 API 來實現 Tooltip 文案的配置;

一週後,又需要加減按鈕支持 Icon 自定義,我們可能會添加 xxxButtonText 之類的 API 來滿足;

又過了 2 周,我們又想支持 Tooltip 展示方位配置,避免遮擋核心內容展示,我們可能會添加 xxxButtonTooltipPlacement 。。。

日復一日,組件 API 數快速擴展,最後,維護者發現實在忍受不了了,決定嘗試使用 Render Props 設計,以此一勞永逸,於是新增了 xxxButtonRender 支持加減按鈕自定義函數渲染。

我們發現,通過這麼做,一個簡單的組件變得日趨複雜,不僅僅存在功能冗餘實現,而且後面還要考慮功能擴展以及向下兼容,臉上的痛苦面具也逐漸明顯。

另外,對於使用者,當想使用一個組件發現有幾頁的 API 數量時,也會淺嘆一聲,功能難以檢索到,而且大部分可能都不需要,面對性能優化也難以入手。

「產品:如何快速打造好用定製的品牌」 「UI」 「?」

對於一個產品,最重要的一點就是塑造產品本身的品牌形象和產品特色。對於用戶最直接接觸的 UI 交互,那更是至關重要。那麼**「如何快速打造好用定製的品牌 UI 呢?」**

還是以數字加減器舉例,那麼,它的好用可能體現在它具備較爲完善且好用的能力。

對於它的定製,可能體現在它 UI 視圖層上的差異化。如下圖,僅僅是 Counter 這種小組件,就有五花八門的 UI 形態。

Headless UI 的解法

從上面的分析我們可以看到,「UI」 「是一個自由度非常高的玩意,而構建 UI 是一種非常品牌化和定製化的體驗。」

那麼,我們能不能**「只需複用組件的交互邏輯,佈局和樣式完全自定義」**呢?顯然,Headless UI 就是幹這件事情的。

對於 Headless UI 組件,我們要做到第一件事,就是分析和抽離組件的狀態以及交互邏輯。對於 Counter 組件,它的狀態邏輯大致如下:

我們把這些狀態邏輯收斂到一個叫 useCounter 的 React Hook 中。它接收用戶傳入的功能 API 設置,然後返回一套已處理過的全新 API。

對於用戶而言,我們只需把返回的 API 賦予到想賦予的標籤上,那麼就得到了一個**「只帶交互能力的無頭組件。」**

最後,我們結合設計稿進行 UI 還原,對編寫自定義樣式,最終就能實現一個全新數字加減器組件了;

另外,我們還可以將標籤重新排版,然後樣式改吧改吧,將按鈕絕對定位一下,最終就能實現一個數字輸入框組件;

除此之外,我們還可以基於它封裝,比如原本的最大值表示總頁數,插入到標籤中間,樣式再改吧改吧,就能實現了一個迷你版的分頁器組件了。

可以看到,通過 Headless UI 的設計思路,我們最終產出了一個叫 useCounter 的 React Hook,「通過它,我們不用關心組件最爲複雜且最通用的部分 ---- 交互邏輯,而是把它交給組件維護者管理;而對於經常變化需要定製的 UI 部分完全由我們自由發揮,從而實現最大化地滿足業務高定製擴展的訴求,同時,也儘可能實現代碼的充分複用。」

Headless UI 的優與劣

這裏我們簡單梳理下 Headless UI 的優勢和劣勢,以及目前建議的適用場景,方便大家做技術選型和學習。

優勢

可以看到 headless 的優勢也非常明顯,因爲它更抽象,所以它擁有非常強大的**「定製擴展能力:支持標籤排版、元素組合,內容插入、樣式定義等等都能滿足。」**

從上面可以看到,組件的狀態邏輯可以儘可能達到最大化複用,幫助我們減小包體積,增強整體可維護性。

因爲基本都是邏輯,對於事件回調、React 運行管理等都可以快速模擬實現單測編寫和迴歸;而 UI 部分,一般容易變化,且不容易出 bug,可以避免測試。

劣勢

抽象層次越高,編寫難度越大。對於這樣 headless 組件,我們關注的組件 API 設計和交互邏輯抽離,這非常考驗開發者的組件設計能力。

UI 層完全自定義,存在一定開發成本,因此需要評估好投入產出,對於沒有特別高要求的 2b 業務的話,還是建議使用 Ant Design 這樣自帶 UI 規範的組件庫進行開發。

Headless UI 的生態與展望

社區生態

關於組件,目前在國外已經有些探索和實踐的案例,比如 React-Popper、React-Hook-Form、TanStack-Table,三個是組件庫 “三大難”,它們 stars (均上萬)和活躍度都非常高,未來基於 headless UI 設計實踐的組件只會越來越多。

關於組件庫,我目前看到的比較不錯的實踐就是 Chakra-UI 組件庫,整個組件庫採用分層架構(這裏以數字輸入框組件爲例):

注意:其實一個組件拆分成多個必要的原子組件構成,其實也算是 Headless UI 的一種實踐形態,把交互邏輯生效的 API 直接綁定在必要的元素標籤上,然後以原子組件暴露出來,標籤的排版和樣式修改也完全可以由用戶自定義。

另外,在 React Next 2022 大會上,也有嘉賓分享介紹 Headless UI 相關的理念,整個社區目前都處在持續發酵的階段。

未來展望

「個人認爲 Headless」 「UI」 「是未來 React 組件庫底層的最佳實踐。」

對於組件庫而言,可能大家都不需要讀書借鑑了,而是都使用同一套組件的底層狀態以及交互邏輯,在 UI 層以及細節上再進行品牌、場景定製化擴展。

總結

那麼,以上就是關於 headless 設計理念的全部內容。「通過 Headless」 「UI」 「,我們可以快速複用組件的狀態以及交互邏輯,對於佈局和樣式實現完全自定義」

另外,「Headless」 「UI」 「是一個組件庫設計的新思路,也是未來組件庫必然的趨勢」。對於前端同學而言,學習瞭解它也顯得尤爲重要。

值得一提的是,在日常開發中,我們也可以嘗試借鑑這樣的思路,「將通用狀態邏輯抽離出去,方便複用,幫助我們在日常開發提效」。比如:常見的篩選過濾、分頁請求列表數據的邏輯等;甚至,我們還可以將業務邏輯同 UI 交互進行抽離,比如:在**「多端場景(Web」** **「PC」** **「端、小程序端、RN 端)複用同」**一套業務邏輯代碼,實現業務邏輯複用和統一,以此大大提高我們的生產力。

參考

作者:不敗花丶

Github:https://github.com/Flcwl

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/HjUpZ7P7sGcgE9NJNjaxlQ