找回密码
 立即注册
首页 业界区 业界 Shadcn UI 实战:打造可维护的企业级组件库 ...

Shadcn UI 实战:打造可维护的企业级组件库

挡缭 3 天前
"我们真的需要自己写一套组件库吗?"上周的技术评审会上,我正在和团队讨论组件库的选型。作为一个快速发展的创业公司,我们既需要高质量的组件,又想保持灵活的定制能力。在对比了多个方案后,我们选择了 shadcn/ui 这个相对较新的解决方案。
说实话,最开始我对这个决定也有些担忧。毕竟相比 Ant Design 这样的成熟方案,shadcn/ui 的知名度确实不高。但经过一个月的实践,这个选择让我们收获了意外的惊喜。
为什么选择 shadcn/ui?

传统组件库给我们带来了一些困扰:

  • 样式难以深度定制
  • 打包体积大
  • 版本升级困难
  • 组件逻辑难以调整
就像租房和买房的选择一样,使用第三方组件库就像租房,虽然能快速入住,但想改造却处处受限。而 shadcn/ui 的方案,更像是买了一栋毛坯房,虽然需要自己装修,但能完全掌控每个细节。
项目实践

1. 初始化配置

首先,我们需要建立一个良好的组件开发基础:
  1. // components.json
  2. {
  3.   "$schema": "https://ui.shadcn.com/schema.json",
  4.   "style": "default",
  5.   "rsc": true,
  6.   "tailwind": {
  7.     "config": "tailwind.config.js",
  8.     "css": "app/globals.css",
  9.     "baseColor": "slate",
  10.     "cssVariables": true
  11.   },
  12.   "aliases": {
  13.     "components": "@/components",
  14.     "utils": "@/lib/utils"
  15.   }
  16. }
  17. // tailwind.config.js
  18. const { fontFamily } = require('tailwindcss/defaultTheme')
  19. /** @type {import('tailwindcss').Config} */
  20. module.exports = {
  21.   darkMode: ['class'],
  22.   content: ['app/**/*.{ts,tsx}', 'components/**/*.{ts,tsx}'],
  23.   theme: {
  24.     container: {
  25.       center: true,
  26.       padding: '2rem',
  27.       screens: {
  28.         '2xl': '1400px'
  29.       }
  30.     },
  31.     extend: {
  32.       colors: {
  33.         border: 'hsl(var(--border))',
  34.         input: 'hsl(var(--input))',
  35.         ring: 'hsl(var(--ring))',
  36.         background: 'hsl(var(--background))',
  37.         foreground: 'hsl(var(--foreground))',
  38.         primary: {
  39.           DEFAULT: 'hsl(var(--primary))',
  40.           foreground: 'hsl(var(--primary-foreground))'
  41.         }
  42.       },
  43.       borderRadius: {
  44.         lg: 'var(--radius)',
  45.         md: 'calc(var(--radius) - 2px)',
  46.         sm: 'calc(var(--radius) - 4px)'
  47.       },
  48.       fontFamily: {
  49.         sans: ['var(--font-sans)', ...fontFamily.sans]
  50.       }
  51.     }
  52.   }
  53. }
复制代码
2. 组件定制

shadcn/ui 最大的特点是它的组件是可以复制到项目中的。这让我们能够根据业务需求进行深度定制:
  1. // components/ui/button.tsx
  2. import * as React from 'react'
  3. import { Slot } from '@radix-ui/react-slot'
  4. import { cva, type VariantProps } from 'class-variance-authority'
  5. import { cn } from '@/lib/utils'
  6. // 扩展按钮变体
  7. const buttonVariants = cva('inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', {
  8.   variants: {
  9.     variant: {
  10.       default: 'bg-primary text-primary-foreground hover:bg-primary/90',
  11.       destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
  12.       outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
  13.       secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
  14.       ghost: 'hover:bg-accent hover:text-accent-foreground',
  15.       link: 'underline-offset-4 hover:underline text-primary',
  16.       // 添加自定义变体
  17.       brand: 'bg-brand-500 text-white hover:bg-brand-600',
  18.       success: 'bg-green-500 text-white hover:bg-green-600'
  19.     },
  20.     size: {
  21.       default: 'h-10 px-4 py-2',
  22.       sm: 'h-9 rounded-md px-3',
  23.       lg: 'h-11 rounded-md px-8',
  24.       icon: 'h-10 w-10'
  25.     }
  26.   },
  27.   defaultVariants: {
  28.     variant: 'default',
  29.     size: 'default'
  30.   }
  31. })
  32. // 扩展按钮属性
  33. interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
  34.   asChild?: boolean
  35.   loading?: boolean
  36.   icon?: React.ReactNode
  37. }
  38. const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, loading, icon, children, ...props }, ref) => {
  39.   const Comp = asChild ? Slot : 'button'
  40.   return (
  41.     <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>
  42.       {loading && <LoadingSpinner className='mr-2 h-4 w-4 animate-spin' />}
  43.       {icon && {icon}}
  44.       {children}
  45.     </Comp>
  46.   )
  47. })
  48. Button.displayName = 'Button'
复制代码
3. 主题定制

我们实现了一个灵活的主题切换系统:
  1. // lib/themes.ts
  2. export const themes = {
  3.   light: {
  4.     '--background': '0 0% 100%',
  5.     '--foreground': '222.2 84% 4.9%',
  6.     '--primary': '222.2 47.4% 11.2%',
  7.     '--primary-foreground': '210 40% 98%'
  8.     // ... 其他颜色变量
  9.   },
  10.   dark: {
  11.     '--background': '222.2 84% 4.9%',
  12.     '--foreground': '210 40% 98%',
  13.     '--primary': '210 40% 98%',
  14.     '--primary-foreground': '222.2 47.4% 11.2%'
  15.     // ... 其他颜色变量
  16.   },
  17.   // 添加自定义主题
  18.   brand: {
  19.     '--background': '0 0% 100%',
  20.     '--foreground': '222.2 84% 4.9%',
  21.     '--primary': '220 90% 56%',
  22.     '--primary-foreground': '210 40% 98%'
  23.   }
  24. }
  25. // components/theme-provider.tsx
  26. const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
  27.   const [theme, setTheme] = useState('light')
  28.   useEffect(() => {
  29.     const root = document.documentElement
  30.     const themeVars = themes[theme as keyof typeof themes]
  31.     Object.entries(themeVars).forEach(([key, value]) => {
  32.       root.style.setProperty(key, value)
  33.     })
  34.   }, [theme])
  35.   return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>
  36. }
复制代码
4. 组件封装

基于 shadcn/ui 的基础组件,我们封装了一些业务组件:
  1. // components/business/data-table.tsx
  2. import { Table } from '@/components/ui/table'
  3. import { Button } from '@/components/ui/button'
  4. import { Input } from '@/components/ui/input'
  5. import { useState } from 'react'
  6. interface DataTableProps<T> {
  7.   data: T[]
  8.   columns: Column[]
  9.   onEdit?: (record: T) => void
  10.   onDelete?: (record: T) => void
  11. }
  12. export function DataTable<T>({ data, columns, onEdit, onDelete }: DataTableProps<T>) {
  13.   const [searchText, setSearchText] = useState('')
  14.   const filteredData = data.filter(record =>
  15.     columns.some(column => {
  16.       const value = record[column.key as keyof T]
  17.       return String(value).toLowerCase().includes(searchText.toLowerCase())
  18.     })
  19.   )
  20.   return (
  21.    
  22.       
  23.         <Input placeholder='搜索...' value={searchText} onChange={e => setSearchText(e.target.value)} className='max-w-sm' />
  24.       
  25.       <Table>
  26.         <Table.Header>
  27.           {columns.map(column => (
  28.             <Table.Column key={column.key}>{column.title}</Table.Column>
  29.           ))}
  30.           <Table.Column>操作</Table.Column>
  31.         </Table.Header>
  32.         <Table.Body>
  33.           {filteredData.map((record, index) => (
  34.             <Table.Row key={index}>
  35.               {columns.map(column => (
  36.                 <Table.Cell key={column.key}>{record[column.key as keyof T]}</Table.Cell>
  37.               ))}
  38.               <Table.Cell>
  39.                
  40.                   {onEdit && (
  41.                     <Button size='sm' variant='outline' onClick={() => onEdit(record)}>
  42.                       编辑
  43.                     </Button>
  44.                   )}
  45.                   {onDelete && (
  46.                     <Button size='sm' variant='destructive' onClick={() => onDelete(record)}>
  47.                       删除
  48.                     </Button>
  49.                   )}
  50.                
  51.               </Table.Cell>
  52.             </Table.Row>
  53.           ))}
  54.         </Table.Body>
  55.       </Table>
  56.    
  57.   )
  58. }
复制代码
实践效果

经过一个月的使用,我们获得了显著的收益:

  • 打包体积减少了 60%
  • 组件定制更加灵活
  • 开发效率提升
  • 代码可维护性增强
最让我印象深刻的是一位同事说:"终于可以随心所欲地修改组件了,不用再为覆盖第三方样式发愁。"
经验总结

使用 shadcn/ui 的过程让我们学到:

  • 组件库不一定要追求"拿来即用"
  • 掌控组件源码比黑盒封装更有优势
  • 样式系统的一致性很重要
  • 渐进式地构建组件库是个好方法
就像装修房子,虽然前期投入较大,但最终得到的是一个完全符合需求的解决方案。
写在最后

shadcn/ui 给了我们一个全新的视角:组件库可以是一系列最佳实践的集合,而不仅仅是一个封装好的产品。正如那句话说的:"授人以鱼不如授人以渔",shadcn/ui 不仅给了我们组件,更教会了我们如何构建组件。
有什么问题欢迎在评论区讨论,让我们一起探讨组件库开发的更多可能!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册