重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
react-composition by s-hiraoku/synapse-a2a
npx skills add https://github.com/s-hiraoku/synapse-a2a --skill react-composition通过组合而非配置来构建灵活的组件 API。
组合优于配置。 当一个组件需要新的行为时,答案几乎从来不是“添加一个布尔属性”。相反,应该将更小的部分组合在一起。
// 反面示例:布尔属性爆炸
<Modal
hasHeader
hasFooter
hasCloseButton
isFullScreen
isDismissable
hasOverlay
centerContent
/>
// 正面示例:按需组合
<Modal>
<Modal.Header>
<Modal.Title>设置</Modal.Title>
<Modal.Close />
</Modal.Header>
<Modal.Body>...</Modal.Body>
<Modal.Footer>
<Button onClick={save}>保存</Button>
</Modal.Footer>
</Modal>
通过上下文共享隐式状态。每个子组件都具有独立的意义。
// 1. 定义共享上下文
interface TabsContextValue {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
function useTabs() {
const ctx = use(TabsContext); // React 19
if (!ctx) throw new Error('useTabs must be used within <Tabs>');
return ctx;
}
// 2. 根组件持有状态
function Tabs({ defaultTab, children }: { defaultTab: string; children: React.ReactNode }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext value={{ activeTab, setActiveTab }}>
<div role="tablist">{children}</div>
</TabsContext>
);
}
// 3. 子组件消费上下文
function TabTrigger({ value, children }: { value: string; children: React.ReactNode }) {
const { activeTab, setActiveTab } = useTabs();
return (
<button
role="tab"
aria-selected={activeTab === value}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
}
function TabContent({ value, children }: { value: string; children: React.ReactNode }) {
const { activeTab } = useTabs();
if (activeTab !== value) return null;
return <div role="tabpanel">{children}</div>;
}
// 4. 附加子组件
Tabs.Trigger = TabTrigger;
Tabs.Content = TabContent;
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
当组件具有不同的模式时,创建显式的变体组件,而不是使用布尔开关。
// 反面示例:布尔模式
<Input bordered />
<Input underlined />
<Input ghost />
// 正面示例:显式变体
<Input.Bordered placeholder="姓名" />
<Input.Underlined placeholder="姓名" />
<Input.Ghost placeholder="姓名" />
// 实现:共享基础,变体特定样式
function createInputVariant(className: string) {
return forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<InputBase ref={ref} className={cn(className, props.className)} {...props} />
));
}
Input.Bordered = createInputVariant('border border-gray-300 rounded-md px-3 py-2');
Input.Underlined = createInputVariant('border-b border-gray-300 px-1 py-2');
Input.Ghost = createInputVariant('bg-transparent px-3 py-2');
使用 children 进行组合。仅当子组件需要来自父组件的数据时才使用渲染属性。
// 反面示例:本可使用子组件却用了渲染属性
<Card renderHeader={() => <h2>标题</h2>} renderBody={() => <p>内容</p>} />
// 正面示例:子组件组合
<Card>
<Card.Header><h2>标题</h2></Card.Header>
<Card.Body><p>内容</p></Card.Body>
</Card>
// 可接受的情况:子组件需要父组件数据时使用渲染属性
<Combobox>
{({ isOpen, selectedItem }) => (
<>
<Combobox.Input />
{isOpen && <Combobox.Options />}
{selectedItem && <Badge>{selectedItem.label}</Badge>}
</>
)}
</Combobox>
设计上下文接口时,要清晰分离状态、操作和元数据。
interface FormContext<T> {
// 状态(从消费者角度看是只读的)
values: T;
errors: Record<string, string>;
touched: Record<string, boolean>;
// 操作(稳定的引用)
setValue: (field: keyof T, value: T[keyof T]) => void;
setTouched: (field: keyof T) => void;
validate: () => boolean;
submit: () => Promise<void>;
// 元数据
isSubmitting: boolean;
isDirty: boolean;
isValid: boolean;
}
当兄弟组件需要访问状态时,将状态移入提供者。
// 反面示例:属性透传
function Parent() {
const [selected, setSelected] = useState<string | null>(null);
return (
<>
<Sidebar selected={selected} onSelect={setSelected} />
<Detail selected={selected} />
</>
);
}
// 正面示例:共享上下文
function Parent() {
return (
<SelectionProvider>
<Sidebar />
<Detail />
</SelectionProvider>
);
}
React 19 将 ref 作为常规属性传递。
// 之前 (React 18)
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// 之后 (React 19)
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// 之前
const ctx = useContext(ThemeContext);
// 之后 (React 19) — 可在条件语句和循环中使用
const ctx = use(ThemeContext);
| 情况 | 模式 |
|---|---|
| 组件有 3 个以上的布尔布局属性 | 复合组件 |
| 同一组件的多种视觉模式 | 显式变体 |
| 灵活的子组件布局需要父组件数据 | 渲染属性 |
| 兄弟组件共享状态 | 上下文提供者 + 状态提升 |
| 简单自定义插槽 | children 属性 |
| 组件需要命令式 API | useImperativeHandle |
| 避免 | 原因 | 替代方案 |
|---|---|---|
<Component isX isY isZ /> | 组合爆炸,交互不明确 | 复合组件或显式变体 |
renderHeader, renderFooter | 将父级 API 与子级结构耦合 | children + 插槽组件 |
| 深度嵌套的上下文提供者 | 性能差 + 调试噩梦 | 将状态与消费者放在一起,拆分上下文 |
使用 React.cloneElement 进行注入 | 脆弱,包装器会破坏其功能 | 基于上下文的组合 |
| 所有状态使用单个巨型上下文 | 任何更改都会导致所有消费者重新渲染 | 拆分为 StateContext + ActionsContext |
每周安装量
52
代码仓库
首次出现
2026年2月11日
安全审计
安装于
opencode51
gemini-cli50
github-copilot50
codex50
amp50
kimi-cli50
Build flexible component APIs through composition instead of configuration.
Composition over configuration. When a component needs a new behavior, the answer is almost never "add a boolean prop." Instead, compose smaller pieces together.
// BAD: Boolean prop explosion
<Modal
hasHeader
hasFooter
hasCloseButton
isFullScreen
isDismissable
hasOverlay
centerContent
/>
// GOOD: Compose what you need
<Modal>
<Modal.Header>
<Modal.Title>Settings</Modal.Title>
<Modal.Close />
</Modal.Header>
<Modal.Body>...</Modal.Body>
<Modal.Footer>
<Button onClick={save}>Save</Button>
</Modal.Footer>
</Modal>
Share implicit state through context. Each sub-component is independently meaningful.
// 1. Define shared context
interface TabsContextValue {
activeTab: string;
setActiveTab: (tab: string) => void;
}
const TabsContext = createContext<TabsContextValue | null>(null);
function useTabs() {
const ctx = use(TabsContext); // React 19
if (!ctx) throw new Error('useTabs must be used within <Tabs>');
return ctx;
}
// 2. Root component owns the state
function Tabs({ defaultTab, children }: { defaultTab: string; children: React.ReactNode }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext value={{ activeTab, setActiveTab }}>
<div role="tablist">{children}</div>
</TabsContext>
);
}
// 3. Sub-components consume context
function TabTrigger({ value, children }: { value: string; children: React.ReactNode }) {
const { activeTab, setActiveTab } = useTabs();
return (
<button
role="tab"
aria-selected={activeTab === value}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
}
function TabContent({ value, children }: { value: string; children: React.ReactNode }) {
const { activeTab } = useTabs();
if (activeTab !== value) return null;
return <div role="tabpanel">{children}</div>;
}
// 4. Attach sub-components
Tabs.Trigger = TabTrigger;
Tabs.Content = TabContent;
When components have distinct modes, create explicit variant components instead of boolean switches.
// BAD: Boolean modes
<Input bordered />
<Input underlined />
<Input ghost />
// GOOD: Explicit variants
<Input.Bordered placeholder="Name" />
<Input.Underlined placeholder="Name" />
<Input.Ghost placeholder="Name" />
// Implementation: shared base, variant-specific styles
function createInputVariant(className: string) {
return forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<InputBase ref={ref} className={cn(className, props.className)} {...props} />
));
}
Input.Bordered = createInputVariant('border border-gray-300 rounded-md px-3 py-2');
Input.Underlined = createInputVariant('border-b border-gray-300 px-1 py-2');
Input.Ghost = createInputVariant('bg-transparent px-3 py-2');
Use children for composition. Only use render props when the child needs data from the parent.
// BAD: Render prop when children would work
<Card renderHeader={() => <h2>Title</h2>} renderBody={() => <p>Content</p>} />
// GOOD: Children composition
<Card>
<Card.Header><h2>Title</h2></Card.Header>
<Card.Body><p>Content</p></Card.Body>
</Card>
// ACCEPTABLE: Render prop when child needs parent data
<Combobox>
{({ isOpen, selectedItem }) => (
<>
<Combobox.Input />
{isOpen && <Combobox.Options />}
{selectedItem && <Badge>{selectedItem.label}</Badge>}
</>
)}
</Combobox>
Design context interfaces with clear separation of state, actions, and metadata.
interface FormContext<T> {
// State (read-only from consumer perspective)
values: T;
errors: Record<string, string>;
touched: Record<string, boolean>;
// Actions (stable references)
setValue: (field: keyof T, value: T[keyof T]) => void;
setTouched: (field: keyof T) => void;
validate: () => boolean;
submit: () => Promise<void>;
// Metadata
isSubmitting: boolean;
isDirty: boolean;
isValid: boolean;
}
Move state into provider when siblings need access.
// BAD: Prop drilling
function Parent() {
const [selected, setSelected] = useState<string | null>(null);
return (
<>
<Sidebar selected={selected} onSelect={setSelected} />
<Detail selected={selected} />
</>
);
}
// GOOD: Shared context
function Parent() {
return (
<SelectionProvider>
<Sidebar />
<Detail />
</SelectionProvider>
);
}
React 19 passes ref as a regular prop.
// Before (React 18)
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input ref={ref} {...props} />
));
// After (React 19)
function Input({ ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// Before
const ctx = useContext(ThemeContext);
// After (React 19) — works in conditionals and loops
const ctx = use(ThemeContext);
| Situation | Pattern |
|---|---|
| Component has 3+ boolean layout props | Compound components |
| Multiple visual modes of same component | Explicit variants |
| Parent data needed in flexible child layout | Render prop |
| Siblings share state | Context provider + state lifting |
| Simple customization of a slot | children prop |
| Component needs imperative API | useImperativeHandle |
| Avoid | Why | Instead |
|---|---|---|
<Component isX isY isZ /> | Combinatorial explosion, unclear interactions | Compound components or explicit variants |
renderHeader, renderFooter | Couples parent API to child structure | children + slot components |
| Deeply nested context providers | Performance + debugging nightmare | Colocate state with consumers, split contexts |
React.cloneElement for injection | Fragile, breaks with wrappers | Context-based composition |
Weekly Installs
52
Repository
First Seen
Feb 11, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode51
gemini-cli50
github-copilot50
codex50
amp50
kimi-cli50
前端设计系统技能:生产级UI设计、设计令牌与可访问性指南
8,500 周安装
| Single mega-context for all state | Every consumer re-renders on any change | Split into StateContext + ActionsContext |