关于 react-composable-components
面向 React 开发者的组件设计技能,介绍如何通过复合组件、共享 Context 和灵活属性构建小型可复用组件,并减少不必要的重新渲染。
主要功能
- 01复合组件模式
- 02使用 React Context 共享状态
- 03透传 HTML 属性和 className
- 04使用 clsx 与 twMerge 合并类名
- 05转发 ref
- 06拆分内联渲染逻辑
适用场景
- 01设计可复用的标签页或手风琴组件
- 02拆分庞大的 React 组件
- 03允许使用者自定义组件布局
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jonmumm/ skills --skill react-composable-components安装后在 Claude Code 中输入/react-composable-components确认已加载。
详细介绍
React 可组合组件
用于编写小巧、专注且高度可组合的 React 组件的模式。
核心理念
管理过多状态并渲染多个不同 UI 区域的大型组件,可读性、可复用性和渲染性能都较差。
避免: 接受数十个属性(hasHeader、showFooter)的庞大单体组件。
建议: 将 UI 拆分为原子化、可组合的部分(复合组件),透明地传递 HTML 属性并合并实用工具类,以便轻松自定义。
1. 提取内联渲染方法
如果组件包含 renderStoryScreen() 之类的方法,请将它们提取为独立的子组件。这样,当状态的某个小部分发生变化时,就能避免整个庞大组件不必要地重新渲染。
// Prefer returning separate components instead of calling inline logic blocks
function App() {
const [screen, setScreen] = useState('START');
return (
<main>
{screen === 'START' && <StartScreen onStart={() => setScreen('STORY')} />}
{screen === 'STORY' && <StoryScreen />}
</main>
);
}
2. 优先使用复合组件,而非配置属性
不要将复杂对象传递给单个组件(<MonolithCard footerButtons={<button>} />),而应公开子组件。这样,使用者就能插入自定义内容,或完全省略某些部分。
export function App() {
return (
<Card className="border-blue-500">
<CardHeader>
<CardTitle>Adventure Begins</CardTitle>
</CardHeader>
<CardContent>
{/* Consumer fully controls the layout and sub-components! */}
<img src="/hero.png" alt="Hero" className="w-full h-auto" />
</CardContent>
</Card>
);
}
3. 使用 Context 共享状态
构建复杂的复合组件(如 Tabs 或 Accordions)时,可使用本地 React Context 在内部共享状态,让使用者无需手动逐层传递属性(isOpen={isOpen} setIsOpen={setIsOpen})。
import { createContext, useContext, useState } from "react";
const TabsContext = createContext<{ activeTab: string; setActiveTab: (v: string) => void } | null>(null);
export function Tabs({ defaultValue, children }) {
const [activeTab, setActiveTab] = useState(defaultValue);
return <TabsContext.Provider value={{ activeTab, setActiveTab }}>{children}</TabsContext.Provider>;
}
export function Tab({ value, children }) {
const ctx = useContext(TabsContext);
const isActive = ctx?.activeTab === value;
return <button onClick={() => ctx?.setActiveTab(value)} className={isActive ? "font-bold" : ""}>{children}</button>;
}
export function TabContent({ value, children }) {
const ctx = useContext(TabsContext);
return ctx?.activeTab === value ? <div>{children}</div> : null;
}
用法:
<Tabs defaultValue="home">
<Tab value="home">Home</Tab>
<Tab value="settings">Settings</Tab>
<TabContent value="home">Home Content!</TabContent>
<TabContent value="settings">Settings Content!</TabContent>
</Tabs>
4. 透明传递属性并合并 ClassName
可组合组件的行为应如同原生 HTML 元素。接受 className 和 ...props,让使用者能够完全掌控组件,同时避免组件 API 过于臃肿。
import { forwardRef } from "react";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
export const Button = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
({ className, variant = "primary", ...props }, ref) => (
<button
ref={ref}
className={cn(
"rounded-md px-4 py-2 transition-colors",
variant === "primary" ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-900",
className
)}
{...props}
/>
)
);
Button.displayName = "Button";
5. 使用 react-twc 编写精简组件
使用 forwardRef 和 className 合并来编写包装组件,容易产生大量样板代码。将样式映射到属性时,可使用 react-twc,它提供类似 styled-components 的 API,并能自动处理 forwardRef 和 className 合并。
import { twc } from "react-twc";
export const Title = twc.h1`text-4xl font-extrabold tracking-tight`;
// Perfect for building Compound Components concisely:
export const Card = twc.div`rounded-xl border bg-card shadow`;
export const CardHeader = twc.div`flex flex-col space-y-1.5 p-6`;
export const CardTitle = twc.h3`font-semibold leading-none tracking-tight`;
export const CardContent = twc.div`p-6 pt-0`;
总结检查清单
- 在所有可复用的 UI 组件中,接受
className和...props。 - 使用
twMerge(clsx(...))或react-twc等工具,自然地将默认类与使用者的覆盖类合并。 - 公开结构化组件(如
Header、Title、Content),而非依赖庞大的配置属性对象。 - 提供
ref转发(React.forwardRef),让父组件可以直接与底层 DOM 元素交互。 - 对需要共享内部状态的复合组件,使用本地 Context。
- 将内联的
renderSomething()方法拆分为真正的 React 组件,以优化渲染。