Back

为什么开发者在 React 项目中转向 shadcn/ui

为什么开发者在 React 项目中转向 shadcn/ui

如果你一直在使用传统的 UI 库(如 Material-UI 或 Chakra UI)构建 React 应用程序,你可能遇到过同样的困扰:与主题覆盖作斗争、处理臃肿的打包体积,以及难以自定义不完全符合设计要求的组件。越来越多的开发者在 shadcn/ui 中找到了解决方案——这是一种根本不同的 React 组件方法,正在改变我们对 UI 库的思考方式。

本文解释了为什么 shadcn/ui React 采用率正在加速增长,其基于 CLI 的组件模型如何工作,以及何时适合你的项目。我们将直接与传统库进行比较,并探讨其优势和权衡。

核心要点

  • shadcn/ui 将组件源代码复制到你的项目中,而不是作为依赖项安装
  • 组件基于 Radix UI 原语构建以确保可访问性,使用 Tailwind CSS 进行样式设计
  • 这种方法提供完全的自定义控制并消除供应商锁定
  • 最适合自定义设计系统、复杂表单和仪表板应用程序
  • 需要 Tailwind CSS 专业知识和手动组件维护
  • 权衡包括设置复杂性和相比成熟库有限的组件生态系统

shadcn/ui 与传统 React 库的不同之处

与你作为 npm 包安装的传统 UI 库不同,shadcn/ui 基于一个根本不同的原则运作:代码所有权。你不是从 node_modules 导入预编译的组件,而是将实际的组件源代码直接复制到你的项目中。

以下是两种方法的区别:

传统库方法:

npm install @mui/material
import { Button } from '@mui/material'

shadcn/ui 方法:

npx shadcn-ui@latest add button
import { Button } from "@/components/ui/button"

关键区别?使用 shadcn/ui,Button 组件的源代码现在存在于你的 components/ui/ 目录中,而不是在 node_modules 中。你完全拥有它。

基于 CLI 的脚手架模型解释

shadcn/ui CLI 是这个系统的核心。当你运行 npx shadcn-ui@latest add button 时,它会:

  1. 下载 Button 组件的 TypeScript 源代码
  2. 将其放置在你指定的组件目录中
  3. 包含所有必要的依赖项和工具
  4. 配置适当的 TypeScript 类型

这不仅仅是复制粘贴——这是智能脚手架。CLI 处理:

  • 依赖解析:自动安装所需的包,如用于样式变体的 class-variance-authority
  • 配置管理:遵循你项目的 Tailwind CSS 设置和 TypeScript 配置
  • 文件组织:在项目间创建一致的文件夹结构
# 在你的项目中初始化 shadcn/ui
npx shadcn-ui@latest init

# 根据需要添加单个组件
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add form

技术基础:Radix UI、Tailwind CSS 和 TypeScript

shadcn/ui React 组件之所以如此出色,归结于它们的技术基础。每个组件都建立在三个支柱之上:

用于可访问性的 Radix UI 原语

Radix UI 提供无样式、可访问的组件原语。这意味着 shadcn/ui 组件继承了:

  • 键盘导航:Tab、方向键和 escape 键处理
  • 屏幕阅读器支持:适当的 ARIA 属性和语义 HTML
  • 焦点管理:逻辑焦点流和适当的焦点捕获
// shadcn/ui Dialog 组件使用 Radix Dialog 原语
import * as DialogPrimitive from "@radix-ui/react-dialog"

const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
// 通过 Tailwind CSS 添加样式

用于样式的 Tailwind CSS

每个 shadcn/ui 组件都专门使用 Tailwind CSS 工具类。这提供了:

  • 一致的设计令牌:颜色、间距和排版遵循你的 Tailwind 配置
  • 响应式设计:内置响应式修饰符无缝工作
  • 轻松自定义:直接编辑 Tailwind 类来更改样式
// 带有 Tailwind 样式的 Button 组件
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        outline: "border border-input bg-background hover:bg-accent",
      },
    },
  }
)

用于开发者体验的 TypeScript

所有组件都包含完整的 TypeScript 定义,提供:

  • 类型安全:在编译时捕获属性错误
  • IntelliSense:组件属性和变体的自动完成
  • 重构支持:安全的重命名和重构

shadcn/ui vs 传统 UI 库:技术比较

让我们在关键维度上检查 shadcn/ui 与成熟库的比较:

开发者控制和自定义

shadcn/ui:

  • 完全的源代码访问权限
  • 直接修改组件文件
  • 没有主题提供者复杂性
  • Tailwind 原生自定义

Material-UI/Chakra UI:

  • 主题覆盖系统
  • CSS-in-JS 抽象
  • 有限的组件内部访问
  • 复杂的自定义 API

打包体积和性能

shadcn/ui:

  • 只包含你实际使用的组件
  • 没有运行时主题处理
  • 最小的 JavaScript 开销
  • 默认更好的 tree-shaking

传统库:

  • 经常包含未使用的组件
  • 运行时主题计算
  • 更大的 JavaScript 包
  • Tree-shaking 效果因库而异

供应商锁定风险

shadcn/ui:

  • 组件成为你代码库的一部分
  • 不依赖于外部包更新
  • 采用后消除迁移风险

传统库:

  • 依赖于包维护者的决策
  • 主要版本中的破坏性更改
  • 迁移复杂性随时间增加

shadcn/ui 表现出色的实际用例

自定义设计系统

在构建独特设计系统时,shadcn/ui 提供了完美的起点。你可以:

  • 修改组件变体以匹配品牌指南
  • 添加自定义属性和行为
  • 在应用程序中保持一致性
  • 在自己的仓库中记录更改
// 为你的设计系统自定义 Button 变体
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium",
  {
    variants: {
      variant: {
        // 你的自定义品牌变体
        brand: "bg-gradient-to-r from-purple-600 to-blue-600 text-white hover:from-purple-700 hover:to-blue-700",
      },
    },
  }
)

表单密集型应用程序

对于具有复杂表单的应用程序,shadcn/ui 的表单组件与 React Hook FormZod 无缝集成:

import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"

const formSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

export function LoginForm() {
  const form = useForm({
    resolver: zodResolver(formSchema),
  })

  const onSubmit = (values) => {
    console.log(values)
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>邮箱</FormLabel>
              <FormControl>
                <Input type="email" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">登录</Button>
      </form>
    </Form>
  )
}

仪表板和管理界面

仪表板应用程序受益于 shadcn/ui 的数据显示组件:

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"

export function UserDashboard({ users }) {
  return (
    <div className="space-y-6">
      <div className="grid gap-4 md:grid-cols-3">
        <Card>
          <CardHeader>
            <CardTitle>总用户数</CardTitle>
          </CardHeader>
          <CardContent>
            <div className="text-2xl font-bold">{users.length}</div>
          </CardContent>
        </Card>
      </div>
      
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>姓名</TableHead>
            <TableHead>状态</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {users.map((user) => (
            <TableRow key={user.id}>
              <TableCell>{user.name}</TableCell>
              <TableCell>
                <Badge variant={user.active ? "default" : "secondary"}>
                  {user.active ? "活跃" : "非活跃"}
                </Badge>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

权衡和考虑因素

维护开销

代码所有权伴随着责任。你需要:

  • 手动更新组件:没有来自包管理器的自动更新
  • 处理安全补丁:监控像 Radix UI 这样的依赖项的更新
  • 保持一致性:确保组件间的更改保持连贯

需要 Tailwind CSS 专业知识

shadcn/ui 假设你熟悉 Tailwind CSS。团队需要:

  • 理解工具优先的 CSS 原则
  • 了解 Tailwind 的响应式和状态修饰符
  • 熟悉自定义 Tailwind 配置

初始设置复杂性

入门需要比传统库更多的配置:

  • Tailwind CSS 设置和配置
  • 路径别名的 TypeScript 配置
  • 理解组件架构

有限的组件生态系统

与成熟的库相比,shadcn/ui 提供:

  • 更少的预构建组件
  • 更少的社区贡献组件
  • 需要从头构建复杂组件

在你的 React 项目中开始使用 shadcn/ui

这里是一个实用的设置指南:

# 创建一个带有 TypeScript 和 Tailwind 的新 Next.js 项目
npx create-next-app@latest my-app --typescript --tailwind --eslint

# 导航到你的项目
cd my-app

# 初始化 shadcn/ui
npx shadcn-ui@latest init

# 添加你的第一个组件
npx shadcn-ui@latest add button card form

初始化过程将:

  1. 配置你的 tailwind.config.js
  2. 添加必要的 CSS 变量
  3. 设置组件路径别名
  4. 创建基本文件夹结构

结论

shadcn/ui 代表了 React UI 开发中向开发者赋权的转变。通过提供源代码所有权、可访问性优先的组件和 Tailwind CSS 集成,它解决了传统 UI 库的许多痛点。这种方法特别适用于自定义设计系统、表单密集型应用程序和熟悉 Tailwind CSS 的团队。

权衡——维护开销和所需的 Tailwind 专业知识——对大多数开发团队来说是可管理的。对于需要高度自定义、长期可维护性或摆脱供应商锁定的项目,shadcn/ui 相比传统组件库提供了令人信服的优势。

今天就通过访问官方文档并遵循安装指南开始使用 shadcn/ui 构建。CLI 使得在现有 React 项目中试验单个组件变得容易,无需承诺完全迁移。

常见问题

是的,shadcn/ui 非常适合企业应用程序,特别是那些需要自定义设计系统或严格可访问性要求的应用程序。代码所有权模型实际上相比依赖外部包更新减少了长期维护风险。

你通过再次运行 CLI add 命令手动更新组件,这将显示差异。然后你可以选择接受更新或保留你的自定义。这让你完全控制何时以及如何更新。

不可以,shadcn/ui 组件专门为 Tailwind CSS 构建。样式系统与 Tailwind 工具紧密集成。如果你偏好其他 CSS 方法,你需要完全重写组件样式。

由于组件存在于你的代码库中,你的应用程序继续正常工作。你拥有代码,可以根据需要维护、修改或替换组件,而不会有任何外部依赖项破坏你的应用程序。

shadcn/ui 基于类似的可访问性原语(Radix UI)构建,但添加了使用 Tailwind CSS 的预样式组件。无头 UI 库给你更多样式灵活性,但需要更多工作来创建生产就绪的组件。shadcn/ui 提供了一个中间地带,具有良好的默认值和易于自定义。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers