mcpField
R
react-composable-components

react-composable-components

github.com/jonmumm开发245 次安装4 天前更新
代码仓库

关于 react-composable-components

面向 React 开发者的组件设计技能,介绍如何通过复合组件、共享 Context 和灵活属性构建小型可复用组件,并减少不必要的重新渲染。

主要功能

  1. 01复合组件模式
  2. 02使用 React Context 共享状态
  3. 03透传 HTML 属性和 className
  4. 04使用 clsx 与 twMerge 合并类名
  5. 05转发 ref
  6. 06拆分内联渲染逻辑

适用场景

  1. 01设计可复用的标签页或手风琴组件
  2. 02拆分庞大的 React 组件
  3. 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 组件,以优化渲染。

评论

支持 Markdown