shadcn/ui 好在哪
- Authors
- Name
- White Play
重新定义组件库
视频版
展开聊它好在哪之前,我想应该先明确定位,我们看它文档自我介绍的第一句话:
This is not a component library. It is how you build your component library.
所以,它和 Ant Design 或 MUI 这种开箱即用的组件库不是一个赛道的。它和你们公司自己搞的那个 KPI 组件库是一个赛道的。两者看重的都是"如何构建你自己的组件库",但我想大概率你们公司的 KPI 组件库不如它,或者你们应该基于它来重构公司的组件库。
在此之前,假设公司想维护自己的组件库,最常见的做法就是 clone 一份 Element UI 到私有仓库里,然后把 el-
前缀改成自己的前缀,接着集中式的魔改组件,最后强迫各个写业务的同学用你们的魔改组件。
业务同学拿到后纷纷抱怨 bug 多,需求也没满足,还不如直接 Element UI 来的痛快。
写组件的同学觉得自己费力不讨好,写业务的负心汉不懂自己的良苦用心。
最后搞得双方都很不爽。
还有更糟糕的,假设最近几个项目着急上线,但因为组件的 Bug 导致各个写业务的同学束手无策,搞不好大家都得加班了。
所以我们应该怎么避免?或者 shadcn 给了我们哪些启示呢?
极简而非简陋
以通知框 Alert 为例。
如果要我封装 Alert,那肯定要先定义开启状态 isOpen
。再监听开启关闭事件,绑定事件回调函数,最后再写一堆逻辑,处理可能的边界情况。
所谓边界情况是指尽量让组件在各种项目中能正常运行。比如为了让通知框精准出现在屏幕中间可能得用:
- Portal:https://zh-hans.react.dev/reference/react-dom/createPortal
- Teleport:https://cn.vuejs.org/guide/built-ins/teleport.html
但用了 Portal 就得考虑组件 DOM 不在 app 根节点下了,那可能会有新的问题,比如窗口抖动或样式丢失等等。
况且就算我在封装组件的过程中把可能的问题都考虑到了,但一到各个业务项目里总会有新的问题。比如有的同学觉得,仅监听开启关闭事件不够用,还希望加点新的事件来满足需求,比如 afterClose
,beforeOpen
,或者希望弹窗支持多少秒后自动关闭、希望支持各种插槽来满足各种定制等等。
那我们看看 shadcn 的 Alert 组件是怎么写的。你会发现就是 div 加样式,一点逻辑都没有。
我们看看它的 Table 组件,要知道主流组件库封装 Table 组件那都涉及到非常复杂的逻辑,但 shadcn 就是原生表格 HTML 加样式。如果你想要复杂的表格交互、渲染,那它直接推荐你去安装 react-table(vue-table)了,那是业界公认的处理复杂表格逻辑的好工具。
你可能觉得这不对啊,难道它不需要考虑各种边界问题吗,或者它不想让自己开箱即用吗?
边界问题是项目的问题,不是组件的问题。比如前文说的 Portal,Portal 导致样式出问题应该是业务项目开发者考虑的事。这种边界问题应该在各个项目里改,组件开发者永远不能考虑周全。
各个项目可能对样式、功能都要有一定程度的定制,组件库也不可能完全做到开箱即用。功能全面、高度定制、开箱即用本来就是不可能三角,与其因为组件库的出错导致各个业务项目变得棘手,不如直接把源码交给业务项目,让他们自己定制逻辑和样式。
当然 shadcn 不是所有组件都是简单的 div 加样式,有的组件还是对交互有要求的。比如右键菜单、轮播图。那 shadcn 就会让你去安装 @radix-ui
以及一些其他主流的第三方插件。
Radix UI 作为无头组件,自己是一点样式都没有的,但它把可访问性和交互逻辑做到了极致。这个轮播图插件也是一样,它是目前最主流的轮播图工具,文件大小远远小于之前主流的 Swiper。
所以可以看出 shadcn 把专业的事交给了专业的人去做。业务有各种定制需求?那就把交互逻辑和源码完全交给业务。
业界有主流的完善的解决方案?那就没必要自己重复造轮子,UI 库该做的事是定义样式和最基本的交互,所以 shadcn 仅仅把分内的事做好了就很了不起了。
前端新手的老师
如果你们团队技术栈比较复杂,比如有一些老项目在用 Vue 2,一些新项目用了 React 或 Vue 3,那显然让一直维护 Vue 2 的同学抓紧学习 React 和 Vue 3 就变得很重要。
再举一个例子,假如你们团队之前一直使用的是原生 CSS 或 SCSS 这种预处理器,那假如新项目要使用 Tailwind CSS 了,怎么才能让团队成员尽快学习如何写好 Tailwind?
我觉得 shadcn 是一个比较好的老师。
它的源码就在手边,所以你使用它组件的同时就不得不经常查看、修改它的组件源码,这可以使你快速掌握现代前端技术栈:
- Tailwind 有哪些常用样式类?Tailwind 一些复杂用法?
- Tailwind 写太长了怎么办?那我们可以看出基于
cn
让 className 做了基本的分类。 - 封装组件时样式冲突怎么办?同样是
cn
函数。 - 怎么让组件支持各种变体?
甚至因为源码简洁实现优雅,我们还可以不知不觉学到:
- 怎么封装 hooks
- 怎么编写 React 组件?
- 原生 DOM 常用属性、编写可访问性组件等等技能
卡脖子
这个组件库与传统组件库第二个大区别是把"复制粘贴"这种组件引用模式发扬光大,开发者不再需要通过 npm install
下载一个包的方式来引入组件了,假如你的项目也在用 Tailwind CSS,那你基本上直接复制粘贴组件源码就可以使用该组件。
所以,复制粘贴要比 npm install
要好吗?我觉得在源码清晰简洁的前提下,答案是肯定的。
首先 npm install
可能因为"美帝卡我脖子"的原因有可能安装失败,又或者本地可以安装成功,但构建服务器上、新同事的电脑上都可能意外安装失败,这种"不可抗力的"意外可能平常不发生,但一旦发生还是比较麻烦的。
当然完全不用包管理是不可能的,但大型组件库因为代码规模比较大,依赖的依赖也很多,往往更容易导致连接超时,而这种复制粘贴方式引用的组件至少一定程度减少下载量。
当然这个好处我觉得有点头痛医脚。大家都知道真正卡我们脖子的人是谁。
按需引用
这种引用方式是真正的按需引用。像非常主流的 Element Plus 其实大家多数还是全局引入,如果想配合 tree-shaking 来按需引入通常还需要额外安装插件 https://www.npmjs.com/package/unplugin-element-plus,但这个插件周下载量只有 2 万,远少于 Element Plus https://www.npmjs.com/package/element-plus 的周下载量(33 万),从而看出在实际项目中真正在乎编译后文件大小的人还是少数的,真正想办法搞优化的是少数,但复制粘贴这种"按需引用"至少无需额外耗费精力。
AI 喜欢
复制粘贴来的代码是属于你的,是项目的源码,它不是 node_modules
里的依赖。在聊 Tailwind CSS 优势的时候我们说过,因为 CSS 和 DOM 混合在一起导致 AI 解读代码不需要切换上下文,从而让 AI 更有效率的理解代码,生成代码。相类似的,因为组件源码就在旁边,AI 就能方便的读取分析,从而提升 AI 生成代码的质量。如果组件代码是放在 node_modules
里,AI 恐怕就很难读取分析了。这在 AI 时代非常重要。