shadcn ui 介绍
无样式组件库 headless ui
在介绍 shadcn ui 之前,我们先来看前几年出现的概念: headless 组件库
你是否碰上这样的场景,使用 antd / element ui 组件库时,设计让你调整个组件样式(比如说日历日期的特殊颜色等)。
这种情况,通常要费很大的力气跟设计沟通,为什么我不能修改这里的样式,即使只是一小点改动。
组件库都是写好了的,而且底层是耦合的代码,这很难进行更改。强行更改的方案都很脏,不够优雅。
在这种情况下,headless 组件库横空出世。简单理解的话,它只写了逻辑代码,而没有样式代码,让我们看个 demo:
js
import { useState } from 'react'
import { Switch } from '@headlessui/react'
function MyToggle() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={setEnabled}
style={{
background-color: enabled ? 'blue' ? 'gray',
position: 'relative',
display: 'inline-flex',
height: 6,
width: 11,
align-items: 'center',
}}
>
<span className="sr-only">Enable notifications</span>
<span
style={{
display: 'inline-block',
height: 4,
width: 4,
backgroung: white
}}
/>
</Switch>
)
}
可以看到,小小一个开关,几乎所有的样式包括开启、关闭等状态的样式都要亲力亲为,手动实现,否则就是原生的样子。
这样做的好处是足够灵活,但坏处就是太麻烦了,要编写大量组件样式。另外 headless 只提供了 10 个组件,远远不够日常项目使用。
基于 headless 思想的组件库 radix ui
为了解决第二个组件不够的问题,又出现了许多基于 headless 封装的组件库。以 Radix UI 为例,它提供了数十个新的组件,已经能满足日常开发使用了。许多小公司自己的组件库 就是基于他们进行开发的,可以很方便的深入定制自己的样式而不用书写大量逻辑。
有了它们,只剩下样式很繁琐这个问题了,接下来有请主角 shadcn ui 登场
打破常规思路的 shadcn ui
如果是你,你会怎么解决上述样式亲力亲为的问题呢?
- 自己封装一个组件库供成员使用(发布组件很麻烦很费时)
- 做一个样式生成器,覆盖大部分组件(工作量不小,容易出冲突)
- 开摆
我能想到的方案都有一定的缺陷,这也是我之前不想用这个的原因,但是 shadcn 给出了自己天才般的设计:
- 组件库搬到本地,而且提供许多默认样式
- 集成 tailwindcss 大幅度简化样式
- 利用 class-variance-authority 进行分化定制
- 傻瓜都能看懂的干净代码,能轻松进行改造
DEMO 时间
你不需要安装任何库,而是直接将组件文件直接加入你的代码之中,也就是把源码下载到项目中!举个例子,你如果想使用 button 组件,那么首先使用命令加入:
cmd
npx shadcn-ui@latest add button
你就会看到,你的代码中多出来一个文件:
这样就可以直接引用 button 组件了。由于代码足够清晰,可以很方便的使用 tailwindcss 对样式进行改造重写,同时可以使用 cva 进行分化定制,学习成本几乎为0。
这里使用了 class-variance-authority 工具来让逻辑更清晰,详情可以查看下文的工具库介绍
一些缺点
- 如果对于定制化要求不那么高,那么用 element、antd 那些开箱即用的组件库会更方便,毕竟这个需要花时间去适应。
- 同事可能会不通知你就改掉本地组件库,不要过分相信其他人的技术力。
shadcn 相关工具库
class-variance-authority
充分利用 tailwindcss,专注于样式封装,可以利用它来编写自己的组件库,例如:
js
import { cva } from "class-variance-authority";
// 第一个参数是预置css,无论哪个分支都会有。variants 变体详情则需要自己定制不同的样式,变体包括意图主体 intent,大小等等
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: [ "bg-blue-500", "text-white", "border-transparent", "hover:bg-blue-600", ],
secondary: [ "bg-white", "text-gray-800", "border-gray-400", "hover:bg-gray-100", ],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
},
defaultVariants: { intent: "primary", size: "medium", },
});
之后就可以通过这种方式来获取到按钮
js
button({ intent: "secondary", size: "small" }); // 输出一堆复合定制预期的 css
项目中具体使用
js
// 是的,你仍然可以加 classname
className={ buttonVariants({ variant, size, className }) }
tailwind-merge
可以处理掉 tailwindcss 类名冲突的情况,优先级按照 tailwind 的进行,如
js
className={ twMerge("w-10 w-20") } // 会合并为 w-20
clsx
classNames 的上位替代,使用率已经超过 classNames。足够小巧,贴一段官方代码
js
import clsx from 'clsx';
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'
小插件
- lucide-react:拥有 1k+ 图标的轻量图标库
- embla-carousel-react:幻灯片插件
- cmdk:命令插件
- vaul:抽屉插件
- react-hook-form:表单相关能力
- react-resizable-panels:可动态调整的面板
- next-themes:切换主题能力
- sonner:小提示插件
工具库总结
自己开发的其实并不多,shadcn 基于 radix ui,大量集成了优质插件,可以说集百家所长了。如果遇到类似场景,也许能从这些源码里能知不少得到好用且前沿的工具库。