这是一篇不知道题目该是啥的竟品调研文章。最近调研了几个国外优秀的开源组件库,一个是为了之后可能做基础组件库做技术储备,另一个是为了跳出国内 antd 的“舒适圈”,看看对于组件库来说国外的团队是如何设计的。
写在前面的总结
为了便于大家直接看结论,这里我把本来在内网写在最后的结论挪到了开头。(因为从内网看大家其实不太想看我怎么分析源码的,只想看结论以及我会怎么做...)
架构实现
从代码架构来说,大致分为以下几类:
- 单包多组件:以 antd 为代表,所有组件统一版本;
- 多包多组件:国外组件库大部分采用这种架构,每个组件都想有单独的版本,大版本保持一致;
组件颗粒度
从组件颗粒度来说,大致分为以下几类:
- 粗颗粒度:以 antd Modal 为例,这类组件的自定义渲染插槽以 render props 形式提供;
- 细颗粒度:以 Radix-ui 大部分组件为例,这类组件以容器 + children 子组件的形式提供,主张子组件的自由组合,灵活度高;
- 更细的颗粒度:以 React-Spectrum 组件为例,这类组件不仅在 UI 层以细颗粒度的形式提供,还在逻辑成拆分出更细颗粒度的逻辑 hooks;
样式系统设计
从样式系统设计来说,大致分为以下几类:
- 无样式系统:以 rc 、headless ui 为例,这类组件库不提供任何样式文件,全由业务方外部实现;
有自己的设计语言:
- 样式文件:以 antd 为例,这类组件库的样式以外部样式文件的形式提供给组件,使用方可外部通过 className、style 覆盖样式;
- StyledComponents:以 Chakra-ui 为例,这类组件库的样式以 css in js 的形式内置在组件内,使用方可通过自定义主题、className 覆盖样式;
我的看法
其实当我看完 Charkra-ui 的实现后内心已经有了做基础组件库大概的设计。
首先从样式系统来讲,我们重新做组件库的一个目的就是为了避免样式污染,那么假设设计语言已经达成统一的前提下我们是不是可以激进一点的采用 css in js 的形式去编写我们的组件?
接下来从组件的结构来说,做过业务组件的同学可能都知道,业务组件简单来讲就是针对某个场景组合基础组件,而组合的过程中往往只用到基础组件的某一或几个功能(甚至不满足的时候还得很恶心的阉割重写),那么再设计基础组件的时候,是不是可以将本就原子化的基础组件更加原子化的设计?(例如现在国外的那些基础组件)
那么挑战就来了:
- 是不是更加原子化的设计就意味着使用方需要写更多的组合逻辑在业务代码里?
- 采用不兼容 antd api 的设计是不是意味着存量业务的使用方吃屎般的兼容迁移成本?
- ...
不过我倒是想出了一种解决方案,针对上面一些问题,我们是不是可以设计一个适配层来磨平新组件和 antd 之间的 api 差异,适配层根据 antd 的 api 封装一层“皮”来去给存量业务去使用。
那么按照这样子设计,我们的组件库架构是不是就是这个样子:
首先对于业务组件库,底层全部使用新的基础组件重构,对外不改变原有 api。而对于业务组件库的使用方来说,无感知。而对于存量业务,锁死指定版本的 antd api,并提供兼容 antd api 的适配层来平稳过渡,对于增量业务,建议直接使用基础组件库开发。(当然使用适配层我也不知道...)
(以上只代表我个人的观点...)
写在后面的组件库一览
Ant-Design
- 仓库:https://github.com/ant-design...
- 官网:https://ant.design/index-cn
架构模式:UI 实现(antd)+ headless 实现(rc)
- UI 实现:单仓库、单包、多组件,每个组件基于 headless 组件做 UI 和事件封装,按指定版本引入 headless 组件;
- headless 实现:多仓库、单包、单组件,MutiRepo,无样式,仅实现 dom 和逻辑;
antd 最大的特点就是 UI 和 dom + 逻辑分离,对于组件内自定义渲染的场景,多以组件 + render props 的形式存在,以 Modal 为例:
在样式系统上,antd 带给我们的是最常见的一个组件配合一个样式 less 文件。
Chakra-ui
- 仓库:https://github.com/chakra-ui/...
- 官网:https://chakra-ui.com/
- 架构模式:单仓库、多包、多组件,MonoRepo;
Charkra-ui 的实现上,对于 Input、Select 等基础的 ui 组件和常规的组件库没什么太大差异,而对于 Modal、Tooltip、Popover 等存在较多自定义渲染场景的组件,采用可插拔的子组件组合的形式实现,例如 Modal :
在实现上,Modal 不作为实际挂载 dom 的 ui 组件,而是作为容器层分发 props(通过 context 实现),而实际渲染 UI 的部分以子组件来承载,每一个子组件作为一个实体接收容器层派发的 props 处理相对应的 ui 展示和事件,以 ModalCloseButton 为例:
在样式系统的设计上 Charkra-ui 也比较有意思,首先在 css 的使用上,Charkra-ui 采用基于 @emotion/styled 的 StyledComponents。在组件的实现中,通过子包 @chakra-ui/system 下的 factory 函数为每一个原生 dom 元素或者其他组件转换成 StyledComponents。这里以 Input 为例:
而对于 StyledComponents 所需要的基本样式,Charkra-ui 提供了 theme 这个子包作为组件库默认的样式主题,主题包内以 JS 对象形式定义了每一个组件所需要的样式。
然后,为了让默认主题和自定义主题注入到组件内,Charkra-ui 提供了 Provider 来注入主题样式
而 StyleComponents 的使用也使得我们无法合理的从外部覆盖组件内的样式,Charkra-ui 也给我们提供了 className 的 props。
MUI
- 仓库:https://github.com/mui/materi...
- 官网:https://mui.com/zh/
- 架构模式:单仓库、单包、多组件;
在组件的实现上和 Charkra-ui 很类似,在样式系统中也是使用的基于 @emotion/styled 的 StyledComponents。
Headless-ui
- 仓库:https://github.com/tailwindla...
- 官网:https://headlessui.dev/
- 架构模式:单仓库、单包、多组件;
Headless-ui 其实相当于 antd 依赖的 react-component(rc),只关心 dom + 逻辑实现,UI 样式实现交给使用方;
Radix-ui
- 仓库:https://github.com/radix-ui/p...
- 官网:https://www.radix-ui.com/
- 架构模式:单仓库多包多组件,MonoRepo;
Radix-ui 相比于 Charka-ui 在 Select 这种最基本的数据响应组件也采用了可插拔的子组件形式,也就是说,在 Radix-ui 的实现上,子组件的粒度更轻更原子, 以 Select 为例:
在实现上,同样采用容器层通过 context 收集 props,子组件作为 UI 响应 容器层派发的 props。
同样,Radix-ui 也是主张无样式的组件库。
React-Spectrum
- 仓库:https://github.com/tailwindla...
- 官网:https://react-spectrum.adobe....
- 架构模式:单仓库、多包、多组件,MonoRepo;
React-Spectrum 在实现上由三部分组成:
- React-Aria:将组件的行为、可访问性、国际化等可重用的逻辑分离并以 hooks 的形式提供;
- React-Stately:将组件内部使用的一部分状态拆分出 hooks ,以便逻辑重用;
- React-Spectrum:结合 React-Aria、React-Stately、DOM 实现基础组件;
相比于以上组件库,React-Spectrum 不仅将组件从 UI 侧以子组件的形式细粒度的拆分,还在逻辑侧拆分出 React-Aria 和 React-Spectrum 提供 hooks 来实现逻辑拆分解耦。同理,子组件解耦也是通过 context 形式实现。
在样式系统上 React-Spectrum 不建议使用方通过 classNames 或者 styles 来实现覆盖样式的逻辑,因此我们在组件上看不到这两个 props,React-Spectrum 认为由于样式覆盖会导致后续的组件升级带来不可预料的问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。