为什么 shadcn ui 能杀入2023年前端工具库榜首

shadcn ui 介绍

无样式组件库 headless ui

在介绍 shadcn ui 之前,我们先来看前几年出现的概念: headless 组件库

你是否碰上这样的场景,使用 antd / element ui 组件库时,设计让你调整个组件样式(比如说日历日期的特殊颜色等)。

这种情况,通常要费很大的力气跟设计沟通,为什么我不能修改这里的样式,即使只是一小点改动。

组件库都是写好了的,而且底层是耦合的代码,这很难进行更改。强行更改的方案都很脏,不够优雅。

在这种情况下,headless 组件库横空出世。简单理解的话,它只写了逻辑代码,而没有样式代码,让我们看个 demo:

js 复制代码
import { useState } from 'react' 
import { Switch } from '@headlessui/react' 
function MyToggle() { 
    const [enabled, setEnabled] = useState(false) 
    return ( 
        <Switch 
            checked={enabled} 
            onChange={setEnabled} 
            style={{ 
                background-color: enabled ? 'blue' ? 'gray',
                position: 'relative',
                display: 'inline-flex',
                height: 6,
                width: 11,
                align-items: 'center',
            }}
        > 
            <span className="sr-only">Enable notifications</span> 
            <span
                style={{
                    display: 'inline-block',
                    height: 4,
                    width: 4,
                    backgroung: white
                }}
            /> 
        </Switch> 
    ) 
}

可以看到,小小一个开关,几乎所有的样式包括开启、关闭等状态的样式都要亲力亲为,手动实现,否则就是原生的样子。

这样做的好处是足够灵活,但坏处就是太麻烦了,要编写大量组件样式。另外 headless 只提供了 10 个组件,远远不够日常项目使用。

基于 headless 思想的组件库 radix ui

为了解决第二个组件不够的问题,又出现了许多基于 headless 封装的组件库。以 Radix UI 为例,它提供了数十个新的组件,已经能满足日常开发使用了。许多小公司自己的组件库 就是基于他们进行开发的,可以很方便的深入定制自己的样式而不用书写大量逻辑。

有了它们,只剩下样式很繁琐这个问题了,接下来有请主角 shadcn ui 登场

打破常规思路的 shadcn ui

如果是你,你会怎么解决上述样式亲力亲为的问题呢?

  1. 自己封装一个组件库供成员使用(发布组件很麻烦很费时)
  2. 做一个样式生成器,覆盖大部分组件(工作量不小,容易出冲突)
  3. 开摆

我能想到的方案都有一定的缺陷,这也是我之前不想用这个的原因,但是 shadcn 给出了自己天才般的设计:

  1. 组件库搬到本地,而且提供许多默认样式
  2. 集成 tailwindcss 大幅度简化样式
  3. 利用 class-variance-authority 进行分化定制
  4. 傻瓜都能看懂的干净代码,能轻松进行改造

DEMO 时间

你不需要安装任何库,而是直接将组件文件直接加入你的代码之中,也就是把源码下载到项目中!举个例子,你如果想使用 button 组件,那么首先使用命令加入:

cmd 复制代码
npx shadcn-ui@latest add button

你就会看到,你的代码中多出来一个文件:

这样就可以直接引用 button 组件了。由于代码足够清晰,可以很方便的使用 tailwindcss 对样式进行改造重写,同时可以使用 cva 进行分化定制,学习成本几乎为0。

这里使用了 class-variance-authority 工具来让逻辑更清晰,详情可以查看下文的工具库介绍

一些缺点

  1. 如果对于定制化要求不那么高,那么用 element、antd 那些开箱即用的组件库会更方便,毕竟这个需要花时间去适应。
  2. 同事可能会不通知你就改掉本地组件库,不要过分相信其他人的技术力。

shadcn 相关工具库

class-variance-authority

充分利用 tailwindcss,专注于样式封装,可以利用它来编写自己的组件库,例如:

js 复制代码
import { cva } from "class-variance-authority";
// 第一个参数是预置css,无论哪个分支都会有。variants 变体详情则需要自己定制不同的样式,变体包括意图主体 intent,大小等等
const button = cva(["font-semibold", "border", "rounded"], {
    variants: {
        intent: {
            primary: [ "bg-blue-500", "text-white", "border-transparent", "hover:bg-blue-600", ], 
            secondary: [ "bg-white", "text-gray-800", "border-gray-400", "hover:bg-gray-100", ], 
        },
        size: {
            small: ["text-sm", "py-1", "px-2"],
            medium: ["text-base", "py-2", "px-4"], 
        },
    }, 
    defaultVariants: { intent: "primary", size: "medium", },
});

之后就可以通过这种方式来获取到按钮

js 复制代码
button({ intent: "secondary", size: "small" }); // 输出一堆复合定制预期的 css

项目中具体使用

js 复制代码
// 是的,你仍然可以加 classname
className={ buttonVariants({ variant, size, className }) }

tailwind-merge

可以处理掉 tailwindcss 类名冲突的情况,优先级按照 tailwind 的进行,如

js 复制代码
className={ twMerge("w-10 w-20") } // 会合并为 w-20

clsx

classNames 的上位替代,使用率已经超过 classNames。足够小巧,贴一段官方代码

js 复制代码
import clsx from 'clsx';

clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'

clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'

clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'

小插件

  • lucide-react:拥有 1k+ 图标的轻量图标库
  • embla-carousel-react:幻灯片插件
  • cmdk:命令插件
  • vaul:抽屉插件
  • react-hook-form:表单相关能力
  • react-resizable-panels:可动态调整的面板
  • next-themes:切换主题能力
  • sonner:小提示插件

工具库总结

自己开发的其实并不多,shadcn 基于 radix ui,大量集成了优质插件,可以说集百家所长了。如果遇到类似场景,也许能从这些源码里能知不少得到好用且前沿的工具库。

相关推荐
我爱李星璇2 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒6 分钟前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员22 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐24 分钟前
前端图像处理(一)
前端
程序猿阿伟31 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒33 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪41 分钟前
AJAX的基本使用
前端·javascript·ajax
力透键背44 分钟前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js