如何在React组件库中实现带TypeScript类型的动态颜色变体
React组件库实现自定义变体的TypeScript类型校验(无需生成.d.ts文件)
要实现这个需求,核心是利用TypeScript的泛型推导和React上下文类型传递,让组件自动感知用户自定义的变体类型,无需额外生成类型文件。以下是具体实现方案:
1. 定义主题的泛型类型与createTheme函数
首先让createTheme函数能捕获用户传入的变体类型,通过泛型保留这些信息:
// types.ts import type { ReactNode } from 'react'; // 单个组件的变体结构 type ComponentVariants = Record<string, string>; // 主题基础结构,用泛型捕获用户自定义的组件变体 type Theme<TVariants extends Record<string, ComponentVariants>> = { [K in keyof TVariants]: { variants: TVariants[K]; }; }; // 创建主题的函数,自动推导用户传入的变体类型 export function createTheme<TVariants extends Record<string, ComponentVariants>>( theme: Theme<TVariants> ): Theme<TVariants> { return theme; }
2. 创建带类型的组件库上下文
通过React上下文传递主题的类型信息,让子组件能获取自定义的变体类型:
// ComponentLibraryProvider.tsx import { createContext, useContext, ReactNode } from 'react'; import type { Theme } from './types'; // 创建上下文,初始类型为任意主题 const ComponentLibraryContext = createContext<Theme<any> | undefined>(undefined); // Provider组件,接受泛型主题类型 export function ComponentLibraryProvider<TVariants extends Record<string, ComponentVariants>>({ theme, children, }: { theme: Theme<TVariants>; children: ReactNode; }) { return ( <ComponentLibraryContext.Provider value={theme}> {children} </ComponentLibraryContext.Provider> ); } // 自定义Hook,获取主题并断言类型(确保不会返回undefined) export function useTheme<TVariants extends Record<string, ComponentVariants>>() { const theme = useContext(ComponentLibraryContext); if (!theme) { throw new Error('useTheme必须在ComponentLibraryProvider内部使用'); } return theme as Theme<TVariants>; }
3. 实现Button组件的类型校验
在Button组件中,通过useTheme获取主题类型,自动推导variant的可选值:
// Button.tsx import { useTheme } from './ComponentLibraryProvider'; import type { ComponentVariants } from './types'; // Button的Props,通过泛型获取主题中的button变体类型 type ButtonProps<TVariants extends ComponentVariants> = { variant: keyof TVariants; children: React.ReactNode; // 可添加其他Props }; // Button组件,利用泛型让TypeScript自动推导变体类型 export function Button<TVariants extends ComponentVariants>({ variant, children, }: ButtonProps<TVariants>) { const theme = useTheme<{ button: { variants: TVariants } }>(); // 可根据variant获取对应样式 const variantClassName = theme.button.variants[variant]; return <button className={variantClassName}>{children}</button>; }
使用示例
用户使用组件库时,TypeScript会自动校验variant的合法性:
// 用户代码 import { createTheme, ComponentLibraryProvider, Button } from 'componentLibrary'; // 自定义主题,TypeScript自动捕获变体类型 const theme = createTheme({ button: { variants: { primary: 'primary', secondary: 'secondary', danger: 'danger' } } }); function App() { return ( <ComponentLibraryProvider theme={theme}> {/* ✅ 合法,类型校验通过 */} <Button variant="primary">Send</Button> {/* ❌ 非法,TypeScript报错:类型"invalid"不能赋值给类型"primary" | "secondary" | "danger" */} <Button variant="invalid">Error</Button> </ComponentLibraryProvider> ); }
关键原理
- 泛型推导:
createTheme和ComponentLibraryProvider通过泛型保留用户传入的变体类型,避免类型信息丢失。 - 上下文类型传递:子组件通过
useThemeHook获取上下文传递的具体主题类型,自动限制variant的可选值。 - 无代码生成:所有类型均在编译时推导完成,无需额外生成.d.ts文件。
内容的提问来源于stack exchange,提问作者Tim Nikolsky
相关产品推荐
相关产品推荐

