“我们真的需要自己写一套组件库吗?”上周的技术评审会上,我正在和团队讨论组件库的选型。作为一个快速发展的创业公司,我们既需要高质量的组件,又想保持灵活的定制能力。在对比了多个方案后,我们选择了 shadcn/ui 这个相对较新的解决方案。
说实话,最开始我对这个决定也有些担忧。毕竟相比 Ant Design 这样的成熟方案,shadcn/ui 的知名度确实不高。但经过一个月的实践,这个选择让我们收获了意外的惊喜。
为什么选择 shadcn/ui?
传统组件库给我们带来了一些困扰:
- 样式难以深度定制
- 打包体积大
- 版本升级困难
- 组件逻辑难以调整
就像租房和买房的选择一样,使用第三方组件库就像租房,虽然能快速入住,但想改造却处处受限。而 shadcn/ui 的方案,更像是买了一栋毛坯房,虽然需要自己装修,但能完全掌控每个细节。
项目实践
1. 初始化配置
首先,我们需要建立一个良好的组件开发基础:
// components.json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
// tailwind.config.js
const { fontFamily } = require('tailwindcss/defaultTheme')
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: ['app/**/*.{ts,tsx}', 'components/**/*.{ts,tsx}'],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px'
}
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
fontFamily: {
sans: ['var(--font-sans)', ...fontFamily.sans]
}
}
}
}
2. 组件定制
shadcn/ui 最大的特点是它的组件是可以复制到项目中的。这让我们能够根据业务需求进行深度定制:
// components/ui/button.tsx
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 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', {
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline text-primary',
// 添加自定义变体
brand: 'bg-brand-500 text-white hover:bg-brand-600',
success: 'bg-green-500 text-white hover:bg-green-600'
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
})
// 扩展按钮属性
interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps {
asChild?: boolean
loading?: boolean
icon?: React.ReactNode
}
const Button = React.forwardRef(({ className, variant, size, asChild = false, loading, icon, children, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return (
{loading && }
{icon && {icon}}
{children}
)
})
Button.displayName = 'Button'
3. 主题定制
我们实现了一个灵活的主题切换系统:
// lib/themes.ts
export const themes = {
light: {
'--background': '0 0% 100%',
'--foreground': '222.2 84% 4.9%',
'--primary': '222.2 47.4% 11.2%',
'--primary-foreground': '210 40% 98%'
// ... 其他颜色变量
},
dark: {
'--background': '222.2 84% 4.9%',
'--foreground': '210 40% 98%',
'--primary': '210 40% 98%',
'--primary-foreground': '222.2 47.4% 11.2%'
// ... 其他颜色变量
},
// 添加自定义主题
brand: {
'--background': '0 0% 100%',
'--foreground': '222.2 84% 4.9%',
'--primary': '220 90% 56%',
'--primary-foreground': '210 40% 98%'
}
}
// components/theme-provider.tsx
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState('light')
useEffect(() => {
const root = document.documentElement
const themeVars = themes[theme as keyof typeof themes]
Object.entries(themeVars).forEach(([key, value]) => {
root.style.setProperty(key, value)
})
}, [theme])
return {children}
}
4. 组件封装
基于 shadcn/ui 的基础组件,我们封装了一些业务组件:
// components/business/data-table.tsx
import { Table } from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useState } from 'react'
interface DataTableProps {
data: T[]
columns: Column[]
onEdit?: (record: T) => void
onDelete?: (record: T) => void
}
export function DataTable({ data, columns, onEdit, onDelete }: DataTableProps) {
const [searchText, setSearchText] = useState('')
const filteredData = data.filter(record =>
columns.some(column => {
const value = record[column.key as keyof T]
return String(value).toLowerCase().includes(searchText.toLowerCase())
})
)
return (
setSearchText(e.target.value)} className='max-w-sm' />
{columns.map(column => (
{column.title}
))}
操作
{filteredData.map((record, index) => (
{columns.map(column => (
{record[column.key as keyof T]}
))}
{onEdit && (
)}
{onDelete && (
)}
))}
)
}
实践效果
经过一个月的使用,我们获得了显著的收益:
- 打包体积减少了 60%
- 组件定制更加灵活
- 开发效率提升
- 代码可维护性增强
最让我印象深刻的是一位同事说:”终于可以随心所欲地修改组件了,不用再为覆盖第三方样式发愁。”
经验总结
使用 shadcn/ui 的过程让我们学到:
- 组件库不一定要追求”拿来即用”
- 掌控组件源码比黑盒封装更有优势
- 样式系统的一致性很重要
- 渐进式地构建组件库是个好方法
就像装修房子,虽然前期投入较大,但最终得到的是一个完全符合需求的解决方案。
写在最后
shadcn/ui 给了我们一个全新的视角:组件库可以是一系列最佳实践的集合,而不仅仅是一个封装好的产品。正如那句话说的:”授人以鱼不如授人以渔”,shadcn/ui 不仅给了我们组件,更教会了我们如何构建组件。
有什么问题欢迎在评论区讨论,让我们一起探讨组件库开发的更多可能!
如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
暂无评论内容