前言
shadcn/ui在过去的一段时间中在nextjs生态中获得非常大的关注,它以独特的方式解决了我们之前开发中的许多痛点
传统组件库
以下仅是我个人对传统组件库例如antd的观点:
首先他们具有以下特点
- 将所有代码进行打包
- 开箱即用,容易上手
- 关注点分离
于是你只需这样就可以使用
<Button type="dashed">Dashed Button</Button>
而对比shadcn/ui中,你还需要将button.tsx文件单独通过cli或是复制粘贴的方式下载到本地例如:
// button.tsx
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cn, withRef } from '@udecode/cn';
import { type VariantProps, cva } from 'class-variance-authority';
export const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
defaultVariants: {
size: 'default',
variant: 'default',
},
variants: {
isMenu: {
true: 'h-auto w-full cursor-pointer justify-start',
},
size: {
default: 'h-10 px-4 py-2',
icon: 'size-10',
lg: 'h-11 rounded-md px-8',
none: '',
sm: 'h-9 rounded-md px-3',
sms: 'size-9 rounded-md px-0',
xs: 'h-8 rounded-md px-3',
},
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
ghost: 'hover:bg-accent hover:text-accent-foreground',
inlineLink: 'text-base text-primary underline underline-offset-4',
link: 'text-primary underline-offset-4 hover:underline',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
},
},
}
);
export const Button = withRef<
'button',
{
asChild?: boolean;
} & VariantProps<typeof buttonVariants>
>(({ asChild = false, className, isMenu, size, variant, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ className, isMenu, size, variant }))}
ref={ref}
{...props}
/>
);
});
乍一看shadcn复制粘贴的做法似乎既麻烦又看不到什么好处.
先别着急让我们慢慢进行分析:
在传统的组件库中,我们常常会认为将组件代码进行打包发布至npm之上是最好的方式,这样能保证稳定迭代风格统一
我猜还有一个重要的点是可以强制用户去贡献代码,修复一些奇奇怪怪的case
例如有下面的场景(公司真实遇到的)
优点
在我的前公司中沉淀了一套组件库,以及工具集
有一天,部门A的同学发现某组件有了一个很奇怪的边缘问题
于是部门A的同学开始着手去修复这个bug,当他修复完这个bug之后
部门B的同学同样也享受到了这个修复,这样能显著的提高工作效率
问题
但这样又出现了另外一个问题,就是
我想去自定义样式就非常麻烦
一般我们会采取下面的方式,但是我相信在座的各位都被下面的方式整出来奇奇怪怪的bug:
- !important
- :deep样式穿透
解决方案
在我前公司中的解决方案是,联合整个设计部门,对整个组件库进行重新设计
fork整个组件库对其每一个组件进行样式上的修改,这也确实是一种解决方案。
但并不是每一个公司的老板都会看重这样的事情,很多时候老板可能不会在意这些东西,不会给你资源去做这样的事情
该解决方案也会有缺点(都是我工作中遇到的):
- 无法享受开源世界中的版本迭代(需要处理冲突)
- 组件库迭代周期可能慢于产品迭代
- 没人给你提pr
其中最重要的是第二条,假设ui调整了一点组件库的样式,非常简单可能只是修改一下padding
于是你要回到组件库中发起一个pr
然而尴尬的是第二天就要上线了,你的pr还在review
我的做法的将组件复制粘贴到本地
然后这样就丧失了上面的优点,其他部门进行了bug修复我浑然不知
shadcn/ui pattern
现在再让我们回到上面提到的button.tsx文件
不被看好的复制粘贴往往才是最适合我们平时开发的
你直接拿到了所有的样式代码,而巧妙的是功能相关的代码却被打包了
这样你能随意修改样式的同时,还能享受开源世界的最新bug修复
也不用fork整个项目到本地,
甚至搭配monorepos使用单独的packages/ui的工作区对组件库进行单独开发/打包。
关于monorepos这块如何设计 后面会更新另一篇文章.