從一個封閉的工作網絡出來,即將進入開放的世界;將壓抑的心情釋放,迎接光明的未來。生活的拐點可能向下,也可能向上,但人生的曲線只能蜿蜒向前!剛離職,離職的心情都差不多,這次是最輕鬆的一次。在家真的懶得打包行李,該擺出來的擺出來,該丟的就丟掉,只是不捨得這個房間,這個小區以及剛交往的舍友!
這次打開博客,其實是想記錄一下從昨天(2021.12.9)開始思考的問題:最理想的UI庫應該是什麼樣子?
起因:
1、純粹面向樣式:
早晨躺在牀上時,在B站上刷到張鑫旭對的Lulu Ui組件庫的介紹, LuLu UI 組件庫適用場景介紹_嗶哩嗶哩_bilibili ,這個UI不以傳統的業務組件爲目標,以最小侵入,讓程序面向最原生的html 去開發。當然對新手不友好,但它是想給老手充分的自由去操控每一個細節。他一句原話:“致力於解決瀏覽器的視覺表現和交互體驗問題,而不是致力於解決實際的業務功能實現問題”。他舉例,一個應用使用純原生代碼後的樣式醜陋,在簡單啓用Lulu ui後,原業務JS代碼絲毫不需要修改,界面就變美觀了。
去看一下文檔,Lulu有2個特點:
- 我一開始認爲它可能是極端的,不需要添加任何類,直接美化所有的原生dom標籤, 其實不是這樣的,它是一個類bootstrap的框架,簡單的加上 ui-table 就是一個美化後的表格。
- 一些組件是用web component實現的,比較明顯且合理的就是 datetime組件,基於瀏覽器原生<input>輸入框構建, 添加 is=“ui-datetime 這樣的屬性。從控制檯可以看到,input的內部shadow改造很少,整個日期彈窗還是彈在父頁面上的, 並沒有做到input的shadow中去,可能與web component的某些限制有關係!
Lulu的動機和實現都很清晰,也是希望做一個”純粹的面向樣式UI庫“, 這讓我想到了前幾個月看到的 【探索學習】可能是下一代的組件庫 - headlessui_嗶哩嗶哩_bilibili 的視頻。
2、純粹面向組件邏輯
Headless Ui, 它走在另一個極端路上,該庫出自於TailWindLabs,真心符合TailWind的性格!
在通常使用Element 或 AntV等庫時,組件和組件的結構、樣式是綁死的,你選擇使用它,就必須接受組件的樣式和組件實現時的dom結構。當你想完全還原美工設計稿效果時,少不得寫很多覆蓋樣式類,比如添加./deep/ 或添加 !important 來實現交付。其一我們都知道這樣修改樣式不好,其次設計稿與組件差別太大時,由於組件的dom結構固定,往往要放棄該組件!
於是Headless的口號就出來了: 完全的無樣式,給你完整的可訪問控制的UI組件庫,它建議搭配Tailwind css 一起使用。也就是說它每一個組件只是純邏輯,所有的dom結構和dom的樣式,讓使用者自己編寫。比如以<Switch>組件爲例, 整個組件只關注2點:有一個布爾變量和變量切換狀態值的動作。至於你要把組件渲染爲勾選框,還是左右滑動的開關,抑或是兩張靜態的png圖片切換,那是使用者自己編寫了。
如果在項目引入Headless的開發流程極可能是:首先需要一個有經驗的程序員,根據本項目的設計稿把所需要的組件逐一的封裝成傳統的UI組件庫那樣,再供其它人直接引用。這樣就回歸傳遞組件方式了。
思考
做爲一個多年的老前端,見證過jQuery時候的插件輝煌年代,也正享受着目前各類Vue ,React等框架庫的強大功能,也在項目中手擼過許多組件,對於UI組件的開發,目前是實踐Composition API技術的忠實擁躉,還是有一些心得和話要說一說的。
1、開發的基本原則:
前端開發中,大到一整個應用,中到一個頁面,小到一個組件,無一不是由”內在邏輯+外在樣式“構成的,也是我一直強調的原則——最小應用狀態,我叫稱其爲AppCore 模式 ,其實就是隻關注應用的直接狀態數據和應用的動作 。
前端開發也要分成 ”前後端開發,邏輯和樣式要分離“
前端的前端就是編寫dom結構和樣式, 前端的後端開發就是編寫內在的狀態和動作,而動作是觸發狀態的持續變化,從而產生一系列的應用快照。在編程時,只強調數據狀態與界面的直接綁定,一個數據快照對應一個界面快照,杜絕事件的交叉時的事件耦合代碼,避免引入副作用變量和延伸的狀態變量。
事件交叉是指,爲處理兩個或多個事件先後觸發時,需要額外代碼維護這個邏輯。比如一些if語句,一些記錄變量等。其實每個事件都有自己關注的狀態數據,如果引入了上述的額外代碼,必然是未來bug的引子。
延伸的狀態變量是指非指向直接狀態值的變量。有這些典型情況,比如對於某個appCore應用,有一個actived的變量, 開發者爲了綁定圖片效果,寫了一個activedUrl,這種就是延伸變量。 也或者界面有一系列按鈕,於是編寫一個變量: xxxBtnList:[ { id, text, icon,hoverColor,disabled........}] 這樣的東西,其實界面上渲染一組按鈕的話,只有按鈕動作是對應appCore中的動作,而這個xxxBtnLIst變量純粹是爲了綁定而憑空多出來的狀態值,其也歸屬於延伸變量。
App Core模式下,追求極致的數據狀態與界面的直接綁定,追求極致的純數據流,也只有純數據纔有利於函數式的代碼書寫,減少代碼量和bug!
2、目前組件庫長什麼樣
毫無疑問,目前的各個Vue\React組件庫具有強大的功能,衆多的組件,詳細的文檔,能覆蓋複雜的業務場景,基本都是開箱即用,這些自不必說。
但下面是我對現有組件庫的一些吐槽點:
現有的組件庫中的類型:
- 簡單的、避免實現組件:簡單的比如Button ,Icon, Card,el-Image 等,內部基本沒什麼狀態數據和狀態的切換,只是純樣式,此時使用類似bootstrap等CSS庫就可以解決!它們若被封裝爲組件,還會帶來微不足道的一點內存消耗和性能問題。又類似Card的組件,通常需要支持各種 title slot,body slot,開發組件時各種佔位,使用組件時再去填坑。 孰不知DOM中Div等標籤天生支持嵌套,額外引入一個組件,憑空多出來的屬性和概念,實屬浪費,形成額外的學習成本,認知成本!它只需要引入幾個工具類即可!同此理,el-row, el-col , a-row , a-col 等引入更是多餘,Flex佈局百分百可以覆蓋它的應用場景,引入它們只是憑空多了一箇中間層,也帶來額外的學習成本,認知成本!
- 複雜的組件:複雜一點的組件就是table,tree等,它們有一些狀態和動作, 還有許多dom結構和css技巧來支撐組件的樣式展示。這些組件內需要引入許多狀態變量(也即我上述的延伸變量),來服務於樣式綁定,而樣式綁定也深深依賴於組件的邏輯編寫。比如表格的合併表頭,固定表頭,固定列, 列的寬度對齊,虛擬滾動,排序、過濾,格式化單元格。。。。。。所以每個組件內部的邏輯代碼編寫時,無時無刻的得考慮樣式的問題,造成很難嚼得懂。建議嚴格按照appCore的方式組件代碼。
複雜的樣式系統:
無論Element 還是Antv ,通常都是使用了less或sass技術 ,基於自定義css變量等,開發組件以及可定製主題樣式。這塊不太好吐槽,我也只知道這麼多技術,即使我來實現一個庫也會上這些技術。 唯一可吐槽的是某些框架的樣式太複雜了,讓我看不懂😂。 看不懂能怪別人嗎,怪呀,一個Button的樣式能寫2屏長,難道不是有點內捲了嘛。 簡單的應保持簡單化,無論有什麼理由,從簡應該是第一原因!
3、理想的UI框架是什麼
最上面的2個UI框架庫引發我思考,兩者各走一極端,一個只關注於視覺表現和用戶交互體驗,不向業務類組件低頭;另一個只關注組件的邏輯,組件樣式交於用戶,在使用時需要額外的編寫樣式代碼,所以這兩個方案都不是一套完整的方案。做爲一個整體組件方案,必須要達到“開箱即用,覆蓋常用的業務場景,還要達到性能最優,學習成本低,不侵入業務以及向原生的技術看齊,甚至是跨框架開發”。
做爲一名夢想家(非實幹家),我設想了一套UI框架的方法,暫時列述一下,以後有想法,會持續補充。
- 增強的reset.css, 要不叫做 reset-pro.css。
首先是以Lulu UI的出發點,簡單引入CSS,實現一些基礎DOM樣式的優化顯示即可。此一步不要做與業務有關的東西,比如 primary/error colors ,或者 圓形圖片等,保留純粹的html原汁原味。
其次它包含點:1、做重置元素的樣式,重置padding,margin,box-size, 優化默認的form元素,table元素樣式等。
2、添加常用的工具類,比如常見了right,clear,或佈局類 f-row, f-pos-between, fi-1 ,響應式斷點, 或者 hide-lg, show-md 這些,參見Bootstrap、Tailwind.css等做作法。 關於響應式斷點應該不應該設計爲className,應該是仁者見仁的問題,我平時用響應式的少。我是更傾向於在body 上,類似於Modernizr.js的原理,動態的添加響應式類名來實現,讓用戶自己的業務CSS處理,瀏覽器好像是有關於媒介查詢的API(你知道嗎?Javascript也有媒體查詢API - 簡書 (jianshu.com))。 - 增強的HTML,要不叫做html-pro.js吧。
Html 缺失的一些元素,比如彈窗和日期時間,顏色選擇器這種組件,引入一些代碼,統一補齊瀏覽器缺失的功能,爲後續的組件開發,打好基礎。 - 全局的css自定義變量。
首先root節點中,確定一組全局的顏色,間距,border,陰影,動畫時長等 css自定義變量,方便後續的定製主題做準備,之後所有組件使用的一些變量儘量使用全局的css變量。 - 簡單業務組件
1、對於一些簡單業務組件,就完全不要編寫一個組件來與之對應了,直接用class 輔助類來實現,比如 el-button\ el-image\el-link 。
原因1是,每一個組件封裝有性能成本;
原因2是,以vue爲例,我自己試着寫了Button的一個組件,發現它內部沒什麼狀態,假如我給組件定義了一組屬性: {size, type,plain,round,circle,loading,disabled } 。組件內代碼就是把props中接收過來,直接轉成相應的類名,再綁定到模板中去,感覺組件什麼實質性的操作都沒有,操作了個寂寞。 再者disabled屬性更是button原生屬性, native-type="button/submit/reset" 也是原生屬性,我們應該鼓勵讓用戶瞭解這些知識,而不是自己封裝一套屬性掩蓋了原生知識。使用類似bootstrap的類名組件實現這些業務場景<button disabled="disabled" type="button" class="el-button el-button--primary is-disabled"> <span>主要按鈕</span> </button>
2、對於佈局類組件,堅決不實現。 佈局類組件,無論是<el-container> 還是<el-row> <el-col>,都是很多餘的,應該鼓勵用戶學習佈局的css 知識,就像鼓勵用戶瞭解原生屬性一樣。Flex,Grid應該讓大家掌握,組件提供的柵格組件並沒有減輕用戶的認知成本!在增加的reset.css中,添加了常用的輔助類,讓用戶使用這些進行頁面佈局。
3、圖標組件。 主流的框架庫一直是使用字體,較新的Ant design又提出使用svg,我也是支持使用Svg的,它有2,3種優勢是強於字體,但我又不太習慣Ant 封裝的那套引用方式。我建議把每個圖標的path定義爲字符串常量,逐個export出來,方便搖樹。 之後封裝一個<xx-svg :path="star"/> 這裏假設,star就是某一個字符常量。 假如用戶要擴充自己的圖標,只需要靜態引入相應的字符串變量即可! - 複雜業務組件
表單和表單項組件:表單元素在原生的Html中,也是很特殊的存在,有很多屬性和方法。表單的組件的開發,我目前還真沒實際的參與和設計經驗。Element的表單行爲可能最容易讓新手適應的,AntV的Form和Element設計基本一致。Angular提出過2種表單開發的模型,就是“模板驅動”和“響應式表單”,各有優劣點。表單項的開發,文本框、單選、多選、時間、文件等等組件,主要考慮雙向綁定值,校驗規則,曾經 Noform的框架有個ppt,對錶單的分析和研究非常全面,理解得透徹。
彈出類+容器類組件:dialog,popover, toast, tabs, collapse,drawer ,tooltip 等組件
數據類組件: table,tree,list
特殊+裝飾物類組件:時間軸,badge,tag,rate評分,輪播圖,頭像等等。