近日,JavaScript Rising Stars 正式公布 2023 年 JavaScript 明星項目榜單,其中 shadcn/ui 位列榜首,2023 年獲得了 39.5k Star。本文將深入探討 shadcn/ui 是什么、使用方式、實現(xiàn)原理,它憑什么能夠成為年度最火前端項目!
Shadcn UI 與其他 UI 和組件庫如 Material UI、Ant Design、Element UI 的設(shè)計理念截然不同。這些庫一般通過 npm 包提供對組件的訪問,而 Shadcn UI 允許用戶將單個 UI 組件的源代碼直接下載到項目中,提供了更大的靈活性和定制空間。
按照 Shadcn UI 的說法,Shadcn UI 實際上并不是一個組件庫,而是可以復(fù)制并粘貼到應(yīng)用中的可重用組件的集合。
不到一年的時間,Shadcn UI 在 Github 上獲得了超過 40k Star。
Shadcn UI 相比其他組件庫提供了幾個顯著的優(yōu)勢,其中最突出的包括:
在決定是否在未來的項目中采用Shadcn UI之前,有幾個關(guān)鍵因素值得考慮:
Shadcn UI 提供了很多功能,以增強用戶體驗。下面就來看看 Shadcn UI 中的幾個主要的功能:主題和主題編輯器、暗黑模式、CLI和組件。
Shadcn UI 提供了精選的主題,可以輕松地將其復(fù)制并粘貼到應(yīng)用程序中。可以選擇通過代碼庫手動添加主題標記,或者使用 Shadcn UI 的主題編輯器進行更方便的操作。
主題編輯器允許配置各種屬性,如顏色、邊框半徑和模式(明亮或暗黑)。此外,還可以選擇兩種樣式:默認樣式和紐約樣式。每種樣式都具有獨特的組件、動畫和圖標。默認樣式具有較大的輸入字段、lucide-react圖標和用于動畫效果的tailwindcss-animate。而紐約樣式則包括較小的按鈕、帶陰影的卡片和Radix圖標。
使用Shadcn UI的圖形界面,創(chuàng)建自定義主題也非常簡單。編輯器會生成包含自定義樣式定義的代碼片段,只需將其復(fù)制粘貼到應(yīng)用中即可。
shadcn-ui-theme-editor.gif下面是主題編輯器的代碼輸出示例,提供了淺色模式和深色模式的樣式標記:
@layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 221.2 83.2% 53.3%; --radius: 0.3rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 224.3 76.3% 48%; }}
Shadcn UI 支持 Next.js 和 Vite 應(yīng)用的暗黑模式。對于 Next.js 應(yīng)用,Shadcn UI 使用next-themes
來實現(xiàn)暗黑模式切換功能。當(dāng)用戶在明亮模式和暗黑模式之間切換時,應(yīng)用會在明亮和暗黑主題標記之間進行切換。
Shadcn UI 的 CLI 可以將庫與應(yīng)用集成,并添加依賴項以及應(yīng)用相關(guān)的tailwind.config.js
配置。使用CLI還可以輕松地向應(yīng)用程序添加UI組件。
可以選擇手動從文檔中復(fù)制和粘貼每個組件的代碼,或者使用CLI進行添加。CLI提供了優(yōu)秀的開發(fā)者體驗,是使Shadcn UI更易于使用的一個功能。
截至目前,Shadcn UI 擁有 40 個組件,包括 Accordion(手風(fēng)琴)、Skeleton(骨架屏)、Table(表格)和Popover(彈出框)等。通過利用 Shadcn UI 預(yù)構(gòu)建的組件,可以節(jié)省時間,而不必從頭開始構(gòu)建組件。
下面來看看如何將 Shadcn UI 與 Next.js 集成。
首先,通過運行以下命令創(chuàng)建一個新的 Next.js 應(yīng)用:
npx create-next-app@latest my-app --typescript --tailwind --eslint
接下來,運行 init 命令來初始化新項目的依賴項:
npx shadcn-ui@latest init
CLI 將提示進行一些配置。以下是配置問題的示例:
Would you like to use TypeScript (recommended)? no / yesWhich style would you like to use? ? DefaultWhich color would you like to use as base color? ? SlateWhere is your global CSS file? ? ? app/globals.cssDo you want to use CSS variables for colors? ? no / yesWhere is your tailwind.config.js located? ? tailwind.config.jsConfigure the import alias for components: ? @/componentsConfigure the import alias for utils: ? @/lib/utilsAre you using React Server Components? ? no / yes
現(xiàn)在就可以在應(yīng)用中添加組件了,下面就來添加一個按鈕組件。
可以運行以下命令以使用 CLI 添加一個按鈕:
npx shadcn-ui@latest add button
CLI 會自動創(chuàng)建一個組件文件夾,只需要從文件夾中導(dǎo)出它:
import { Button } from "@/components/ui/button"<Button variant="outline">Button</Button>
按鈕組件的 variant 屬性有六種值:default、destructive、outline、secondary、ghost、link。
Shadcn UI 在表單方面不僅提供了 Input、Textarea、Checkbox 和 RadioGroup 等表單組件,還提供了一個Form組件,該組件是 react-hook-form 的包裝器。下面來用 shadcn/ui 創(chuàng)建一個登錄表單。
Form 組件提供了一些功能:
可以運行以下命令來使用表單組件:
npx shadcn-ui@latest add form input
接下來,添加表單組件:
// use client import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import * as z from "zod"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; const FormSchema = z.object({ username: z.string().min(2, { message: "用戶名至少兩個字" }), }); export function InputForm() { const form = useForm({ resolver: zodResolver(FormSchema) }); function onSubmit(data) { return ( <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> {JSON.stringify(data, null, 2)} </pre> ); } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6"> <FormField control={form.control} name="username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input placeholder="Input username" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> ); }
Shadcn UI 組件的通用架構(gòu)如下:
shadcn/ui基于核心原則構(gòu)建,即組件的設(shè)計應(yīng)與其實現(xiàn)分開。因此,shadcn/ui中的每個組件都具有兩層架構(gòu)。即:
在結(jié)構(gòu)和行為層,組件以無頭形式實現(xiàn),這意味著它們的結(jié)構(gòu)組成和核心行為都被封裝在相應(yīng)的表示中,這意味著組件的結(jié)構(gòu)、布局和核心功能都在這一層進行定義和實現(xiàn)。此外,對于一些復(fù)雜的交互,如鍵盤導(dǎo)航和WAI-ARIA標準兼容性,也在這個層面進行考慮和實現(xiàn)。
為了支持這些復(fù)雜的功能和交互,shadcn/ui借助了一些成熟的、無頭(無界面)的UI庫。Radix UI 就是其中的一個關(guān)鍵庫,它在shadcn/ui的代碼庫中占有重要地位。許多常見的組件,如折疊面板(Accordion)、彈出框(Popover)、選項卡(Tabs)等,都是基于 Radix UI 的實現(xiàn)構(gòu)建的。
對于滿足大多數(shù)組件需求,原生瀏覽器元素和Radix UI組件已經(jīng)足夠了。但在某些情況下,需要使用更專業(yè)的無頭UI庫來滿足特定需求。
其中一種情況是表單處理。為了處理表單,shadcn/ui提供了一個基于React Hook Form無頭表單庫的Form組件。這個組件負責(zé)管理表單的狀態(tài),而shadcn/ui則通過組合的方式,利用React Hook Form提供的基元進行了進一步的封裝。
對于表格視圖的處理,shadcn/ui 選擇了 Tanstack React Table 這個無頭表格庫。它的Table
和DataTable
組件都是基于這個庫構(gòu)建的。Tanstack React Table 提供了豐富的 API,用于處理表格視圖的各種交互,如過濾、排序和虛擬化。
另外,對于一些復(fù)雜的日期選擇組件,如日歷視圖、DateTime選擇器和DateRange選擇器,shadcn/ui 選擇了 React Day Picker 這個庫作為基礎(chǔ)組件,以實現(xiàn)這些組件的無頭層。這些組件往往難以正確實現(xiàn),但通過使用 React Day Picker, shadcn/ui 確保了它們的正確性和易用性。
TailwindCSS 是 shadcn/ui 樣式層的核心。顏色、邊框半徑等屬性值作為CSS變量存放在global.css文件中,以便于全局管理。這些變量可以跨設(shè)計系統(tǒng)共享,使用 Figma 等設(shè)計工具時,可以追蹤并同步Figma的變量。
為了區(qū)分組件的樣式,shadcn/ui引入了Class Variance Authority(CVA)。CVA提供了一個強大的API,允許我們?yōu)槊總€組件定制其樣式。
在探討了 shadcn/ui 的架構(gòu)后,下面來深入了解一些組件的具體實現(xiàn),先從最簡單的組件開始。
import * as React from "react";import { cva, type VariantProps } from "class-variance-authority";import { cn } from "@/lib/utils";const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, });export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}function Badge({ className, variant, ...props }: BadgeProps) { return <div className={cn(badgeVariants({ variant }), className)} {...props} />;}export { Badge, badgeVariants };
組件的實現(xiàn)始于對 class-variance-authority 中的 cva
函數(shù)的調(diào)用,它被用于聲明組件的不同變體。
const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", outline: "text-foreground", }, }, defaultVariants: { variant: "default", }, });
cva函數(shù)的第一個參數(shù)為<Badge/>組件的所有變體定義了基本樣式。作為第二個參數(shù),cva接收一個配置對象,該對象規(guī)定了組件的可能變體以及應(yīng)使用的默認變體。需要注意的是,實用樣式采用了tailwind.config.js中定義的設(shè)計系統(tǒng)標記,這使得只需調(diào)整 CSS 變量,就能輕松更新整體的外觀。
調(diào)用cva函數(shù)后會返回另一個函數(shù),該函數(shù)可根據(jù)條件為各個變體應(yīng)用相應(yīng)的樣式。將其存儲在名為badgeVariants的變量中,以便在向組件傳遞變體名稱作為屬性時,能夠利用它應(yīng)用正確的樣式。
接下來,我們可以找到定義組件類型的BadgeProps接口:
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
Badge 組件基于HTML的div元素。為了方便使用,該組件被設(shè)計為div元素的擴展。這一目標通過擴展React.HTMLAttributes<HTMLDivElement>類型來實現(xiàn)。此外,為了滿足不同需求,組件添加了一個variant屬性,允許使用者選擇并呈現(xiàn)所需的組件變體。VariantProps這一輔助類型以枚舉的形式在variant屬性上呈現(xiàn)可用的變體,進一步增強了組件的靈活性和易用性。
function Badge({ className, variant, ...props }: BadgeProps) { return <div className={cn(badgeVariants({ variant }), className)} {...props} />;}
最終,我們得到了定義 Badge 的函數(shù)組件。在此組件中,除了 className 和 variant 之外的所有 props 都被收集到一個對象中,并通過擴展語法傳遞給底層的 div 元素。這使得組件使用者能夠與 div 元素上可用的所有 props 進行交互。
值得注意的是,組件中處理樣式應(yīng)用的方式。variant 的值被傳遞到 badgeVariants 函數(shù)中,該函數(shù)返回一個包含渲染組件變體所需的所有實用程序類名的 class 字符串。此外,還有一個名為 cn 的函數(shù),它將前述函數(shù)的返回值和傳遞到 className 中的值合并,然后計算為 div 元素的 className 屬性。
cn 函數(shù)是 shadcn/ui 提供的一個特殊實用函數(shù),用于管理實用程序類。接下來,我們將深入探討它的實現(xiàn)。
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs));}
這個實用函數(shù)是兩個庫的結(jié)合體,用于管理實用程序類。第一個庫是 clsx
。它提供了通過 className
連接來有條件地應(yīng)用樣式到組件的能力。
import React from "react";const Link = ({ isActive, children }: { isActive: boolean, children: React.ReactNode }) => { return <a className={clsx("text-lg", { "text-blue-500": isActive })}>{children}</a>;};
從上述代碼中可以看到 clsx 獨立使用的情形。在默認情況下,只有 text-lg 實用類被應(yīng)用于 Link 組件。但當(dāng)將 isActive 屬性傳遞給組件并設(shè)置為 true 時,text-blue-500 實用類也會被應(yīng)用于該組件。
然而,在某些情況下,僅使用 clsx 無法實現(xiàn)我們的目標。
import React from "react";import clsx from "clsx";const Link = ({ isActive, children }: { isActive: boolean, children: React.ReactNode }) => { return <a className={clsx("text-lg text-grey-800", { "text-blue-500": isActive })}> {children}</a>;};
在此情況下,元素默認應(yīng)用了顏色實用類 text-grey-800。我們的目標是在 isActive 變?yōu)?nbsp;true 時將文本顏色更改為 blue-500。但由于 CSS 的層疊性質(zhì),Tailwind 中的 text-grey-800 應(yīng)用的顏色樣式無法被修改。
此時就需要使用 tailwind-merge庫。使用 tailwind-merge 修改上述代碼:
import React from "react";import { twMerge } from "tailwind-merge";import clsx from "clsx";const Link = ({ isActive, children }: { isActive: boolean, children: React.ReactNode }) => { return <a className={twMerge(clsx("text-lg text-grey-800", { "text-blue-500": isActive }))}>{children}</a>;};
clsx的輸出現(xiàn)在將通過tailwind-merge進行處理。tailwind-merge將解析類字符串并進行淺層樣式定義合并。這意味著text-grey-800被替換為text-blue-500,從而確保元素能體現(xiàn)出新的條件樣式應(yīng)用。
這種方法有助于確保在實現(xiàn)變體時不會發(fā)生任何樣式?jīng)_突。由于className屬性也經(jīng)過了cn工具的處理,如果需要,可以輕松覆蓋任何樣式。但這也存在一個權(quán)衡之處。使用cn開啟了組件使用者臨時覆蓋樣式的可能性。這將使一定程度的責(zé)任轉(zhuǎn)移到代碼審查步驟上,以驗證cn沒有被濫用。另一方面,如果根本不需要啟用這種行為,可以修改組件僅使用clsx。
在分析Badge組件的實現(xiàn)時,可以發(fā)現(xiàn)一些與 SOLID 原則相關(guān)的模式:
在分析了 Badge 組件的實現(xiàn)之后,我們對 shadcn/ui 的一般架構(gòu)有了更詳細的了解。但這是一個純顯示級別的組件。下面來看看其他一些交互式組件。
下面是 Switch 組件的具體實現(xiàn):
import * as React from "react"import * as SwitchPrimitives from "@radix-ui/react-switch"import { cn } from "@/lib/utils"const Switch = React.forwardRef< React.ElementRef<typeof SwitchPrimitives.Root>, React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>>(({ className, ...props }, ref) => ( <SwitchPrimitives.Root className={cn( "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className )} {...props} ref={ref} > <SwitchPrimitives.Thumb className={cn( "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" )} /> </SwitchPrimitives.Root>))Switch.displayName = SwitchPrimitives.Root.displayNameexport { Switch }
Switch 組件是用于在兩個選項之間進行選擇的交互式組件,與僅用于顯示的 Badge 組件不同。Switch 組件能夠響應(yīng)用戶的操作并切換其狀態(tài),為用戶提供即時的反饋。
用戶與開關(guān)的交互主要通過點擊實現(xiàn)。構(gòu)建一個能夠響應(yīng)指針事件的開關(guān)相對簡單,但要使其也能響應(yīng)鍵盤輸入和屏幕閱讀器,實現(xiàn)起來就更為復(fù)雜。以下是開關(guān)組件的一些預(yù)期行為:
在代碼中,可以看到開關(guān)的實際結(jié)構(gòu)是通過使用 <SwitchPrimitives.Root/> 和 <SwitchPrimitives.Thumb/> 復(fù)合組件構(gòu)建而成。這些組件來自 RadixUI 無頭庫,包含了開關(guān)的預(yù)期行為的所有實現(xiàn)。通過 React.forwardRef 進行構(gòu)建,使得組件能夠與傳入的 ref 綁定,這在需要跟蹤焦點狀態(tài)并與外部庫集成時非常有用。
值得注意的是,RadixUI 組件沒有提供任何樣式。因此,經(jīng)過 cn 實用函數(shù)處理后,樣式直接應(yīng)用于該組件的 className 屬性上。如有需要,還可以使用 cva 為組件創(chuàng)建變體。這種靈活的樣式管理方式使得開發(fā)者能夠根據(jù)項目需求進行定制化設(shè)計,提高用戶體驗。
這里我們討論了 shadcn/ui 的一般架構(gòu),這種實現(xiàn)方式同樣應(yīng)用在 shadcn/ui 的其它組件中。不過,某些組件的行為和實現(xiàn)會稍微復(fù)雜一些,比如:
shadcn/ui 在前端開發(fā)領(lǐng)域中引入了一種創(chuàng)新的范例。它倡導(dǎo)一種新的思維方式,即開發(fā)者可以擁有組件的實現(xiàn)權(quán),而不僅僅是依賴于抽象化的第三方包。通過這種方式,開發(fā)者能夠僅暴露所需的元素,從而更好地控制組件的行為和外觀。
在應(yīng)用設(shè)計系統(tǒng)時,shadcn/ui 鼓勵開發(fā)者跳出預(yù)先構(gòu)建的組件庫所限制的固定 API 表面。相反,它鼓勵開發(fā)者構(gòu)建自己的設(shè)計系統(tǒng),并提供足夠良好的默認設(shè)置,以便開發(fā)者可以根據(jù)自己的需求進行自定義。這種靈活性使得開發(fā)者能夠更好地適應(yīng)不同的項目需求,并在設(shè)計過程中擁有更大的自由度。
Shadcn UI 為開發(fā)者提供了一種全新的體驗,與現(xiàn)有的組件庫相比,它如一陣清風(fēng)般令人耳目一新。它不僅加快了開發(fā)速度,還為開發(fā)者提供了對組件的精細控制,使他們能夠創(chuàng)造出獨特且富有創(chuàng)意的用戶界面。
當(dāng)然,沒有任何庫能滿足所有需求,但基于當(dāng)前的行業(yè)趨勢,Shadcn UI 無疑已成為前端生態(tài)系統(tǒng)中的佼佼者。許多大型公司,如 Vercel,已經(jīng)采納了這一解決方案。例如,Vercel 的 v0 應(yīng)用利用 Shadcn UI、Tailwind CSS 等來生成 UI 代碼,這些代碼可供開發(fā)人員直接復(fù)制并粘貼到其項目中。
盡管 Shadcn UI 仍是一個相對較新的工具,但隨著時間的推移,期待看到其功能和組件的進一步豐富和完善。你是否會考慮在未來的項目中采用 Shadcn UI 呢?
本文鏈接:http://www.www897cc.com/showinfo-26-59677-0.html2023年最火前端項目憑什么是它?
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com