大道至简?Shadcn/ui设计系统初体验(上)

101 阅读3分钟

大道至简?Shadcn/ui设计系统初体验(上)

前言

只要你还在和前端开发打交道,那么搭建项目就不可避免的要找各种各样的组件库,也许是UI设计师的要求,也许是老板“挥斥方遒”的命令,甚至自己造个小玩具,就咱们程序员的平均美术修养而言,最次也要用个Bootstrap,特别是在如今AI已经层出不穷的情况下,我们更需要一个可以和AI好衔接,好开发,好改造的优质组件库,Shadcn/ui 作为Vercel的亲儿子,让我们看看它到底行不行吧。

好衔接:快速安装Shadcn/ui

文档链接:Shadcn/ui Documents

让我们快速把东西先装起来!(本文使用的是React19+Vite,Vuer请自行搜索Shadcn/vue

  1. 安装Vite,创建标准的React项目
pnpm create vite@latest
  1. 添加TailwindCSS
pnpm add tailwindcss @tailwindcss/vite
  1. 编辑配置文件(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,加上根目录即可即可。

  1. 更新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"),
    },
  },
})
  1. 启动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 }

参考