大道至简?Shadcn/ui设计系统初体验(上)
前言
只要你还在和前端开发打交道,那么搭建项目就不可避免的要找各种各样的组件库,也许是UI设计师的要求,也许是老板“挥斥方遒”的命令,甚至自己造个小玩具,就咱们程序员的平均美术修养而言,最次也要用个Bootstrap,特别是在如今AI已经层出不穷的情况下,我们更需要一个可以和AI好衔接,好开发,好改造的优质组件库,Shadcn/ui 作为Vercel的亲儿子,让我们看看它到底行不行吧。
好衔接:快速安装Shadcn/ui
文档链接:Shadcn/ui Documents
让我们快速把东西先装起来!(本文使用的是React19+Vite,Vuer请自行搜索Shadcn/vue)
- 安装Vite,创建标准的React项目
pnpm create vite@latest
- 添加TailwindCSS
pnpm add tailwindcss @tailwindcss/vite
- 编辑配置文件(CSS和tsconfig)
// src/index.css
@import "tailwindcss";
// tsconfig.json
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
// tsconfig.app.json 若使用最新版Vite创建,则无须配置再配置baseUrl,修改path别名即可
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"./@/*": [
"./src/*"
]
}
// ...
}
}
注意:由于”baseUrl”这一关键词将在TypeScript7.0中正式移除,因此在现版本配置tsconfig.app.json时会提示爆红,TypeScript|baseUrl,因此无需填写baseUrl配置像,相应的,只需要修改paths,加上根目录即可即可。
- 更新Vite配置
pnpm add -D @types/node
import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
- 启动CLI
pnpm dlx shadcn@latest init
至此我们已完成安装的全部流程,让我们看看效果!
好开发:按需添加,自由改造
如果仅仅只是“好看”,那对开发者来说绝对是不够的,Shadcn/ui的最大特色就按需引入和零抽象成本的控制。
先说前者,对绝大多数组件库来说好像所谓的按需引入无非就是import {Button} from '…' ,显然这种方法并没有什么不对,但代价就是奇大无比的组件库,即便是在Tree-shaking的优化下,打包后的体积依然非常庞大。
而Shadcn/ui采取了迥然不同的设计策略,需要时通过cli安装:
pnpm dlx shadcn@latest add button
在完成上述操作后,./src/conponent/ui 的路径下出现Button.tsx
此时再通过import语句引入即可.
import { Button } from './components/ui/button'
查看文件后,也可以直接对Button组件进行二次修改
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
参考
- Shadcn/ui 官网
- TypeScript Docs
- Deepseek参与部分内容检查