【日记】重新出发,再入江湖——最理想的UI库应该是什么样子

       从一个封闭的工作网络出来,即将进入开放的世界;将压抑的心情释放,迎接光明的未来。生活的拐点可能向下,也可能向上,但人生的曲线只能蜿蜒向前!刚离职,离职的心情都差不多,这次是最轻松的一次。在家真的懒得打包行李,该摆出来的摆出来,该丢的就丢掉,只是不舍得这个房间,这个小区以及刚交往的舍友!   

      这次打开博客,其实是想记录一下从昨天(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组件库具有强大的功能,众多的组件,详细的文档,能覆盖复杂的业务场景,基本都是开箱即用,这些自不必说。

       但下面是我对现有组件库的一些吐槽点:

     现有的组件库中的类型:

  1. 简单的、避免实现组件:简单的比如Button  ,Icon, Card,el-Image 等,内部基本没什么状态数据和状态的切换,只是纯样式,此时使用类似bootstrap等CSS库就可以解决!它们若被封装为组件,还会带来微不足道的一点内存消耗和性能问题。又类似Card的组件,通常需要支持各种  title slot,body slot,开发组件时各种占位,使用组件时再去填坑。  孰不知DOM中Div等标签天生支持嵌套,额外引入一个组件,凭空多出来的属性和概念,实属浪费,形成额外的学习成本,认知成本!它只需要引入几个工具类即可!同此理,el-row, el-col  , a-row ,  a-col 等引入更是多余,Flex布局百分百可以覆盖它的应用场景,引入它们只是凭空多了一个中间层,也带来额外的学习成本,认知成本!
  2. 复杂的组件:复杂一点的组件就是table,tree等,它们有一些状态和动作, 还有许多dom结构和css技巧来支撑组件的样式展示。这些组件内需要引入许多状态变量(也即我上述的延伸变量),来服务于样式绑定,而样式绑定也深深依赖于组件的逻辑编写。比如表格的合并表头,固定表头,固定列, 列的宽度对齐,虚拟滚动,排序、过滤,格式化单元格。。。。。。所以每个组件内部的逻辑代码编写时,无时无刻的得考虑样式的问题,造成很难嚼得懂。建议严格按照appCore的方式组件代码。

     复杂的样式系统:

       无论Element  还是Antv ,通常都是使用了less或sass技术 ,基于自定义css变量等,开发组件以及可定制主题样式。这块不太好吐槽,我也只知道这么多技术,即使我来实现一个库也会上这些技术。 唯一可吐槽的是某些框架的样式太复杂了,让我看不懂😂。 看不懂能怪别人吗,怪呀,一个Button的样式能写2屏长,难道不是有点内卷了嘛。 简单的应保持简单化,无论有什么理由,从简应该是第一原因!

3、理想的UI框架是什么

      最上面的2个UI框架库引发我思考,两者各走一极端,一个只关注于视觉表现和用户交互体验,不向业务类组件低头;另一个只关注组件的逻辑,组件样式交于用户,在使用时需要额外的编写样式代码,所以这两个方案都不是一套完整的方案。做为一个整体组件方案,必须要达到“开箱即用,覆盖常用的业务场景,还要达到性能最优,学习成本低,不侵入业务以及向原生的技术看齐,甚至是跨框架开发”。

      做为一名梦想家(非实干家),我设想了一套UI框架的方法,暂时列述一下,以后有想法,会持续补充。

  1. 增强的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)
  2. 增强的HTML,要不叫做html-pro.js吧。
    Html 缺失的一些元素,比如弹窗和日期时间,颜色选择器这种组件,引入一些代码,统一补齐浏览器缺失的功能,为后续的组件开发,打好基础。
  3. 全局的css自定义变量。
    首先root节点中,确定一组全局的颜色,间距,border,阴影,动画时长等 css自定义变量,方便后续的定制主题做准备,之后所有组件使用的一些变量尽量使用全局的css变量。
  4. 简单业务组件
    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>
    假如文档中,把按钮的各个状态按className的写法展示,用户复制粘贴,效果也是一样的,没必要为了组件而组件
    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就是某一个字符常量。 假如用户要扩充自己的图标,只需要静态引入相应的字符串变量即可!
  5. 复杂业务组件
    表单和表单项组件:表单元素在原生的Html中,也是很特殊的存在,有很多属性和方法。表单的组件的开发,我目前还真没实际的参与和设计经验。Element的表单行为可能最容易让新手适应的,AntV的Form和Element设计基本一致。Angular提出过2种表单开发的模型,就是“模板驱动”和“响应式表单”,各有优劣点。表单项的开发,文本框、单选、多选、时间、文件等等组件,主要考虑双向绑定值,校验规则,曾经 Noform的框架有个ppt,对表单的分析和研究非常全面,理解得透彻。
    弹出类+容器类组件:dialog,popover, toast,  tabs, collapse,drawer ,tooltip 等组件
    数据类组件: table,tree,list
    特殊+装饰物类组件:时间轴,badge,tag,rate评分,轮播图,头像等等。

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章