skills/react-component-patterns/SKILL.md
React 元件設計模式。用於 Compound Components、Render Props、HOC、Polymorphic Components、Slot Pattern、受控/非受控元件設計。
npx skillsauth add vincent119/ai-rules-kit react-component-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
父元件透過 Context 共享狀態,子元件自由組合:
import { createContext, useContext, useState, type ReactNode } from 'react';
interface AccordionContextValue {
activeIndex: number | null;
toggle: (index: number) => void;
}
const AccordionContext = createContext<AccordionContextValue | null>(null);
function useAccordion() {
const ctx = useContext(AccordionContext);
if (!ctx) throw new Error('useAccordion must be used within Accordion');
return ctx;
}
interface AccordionProps {
children: ReactNode;
defaultIndex?: number;
}
export function Accordion({ children, defaultIndex = null }: AccordionProps) {
const [activeIndex, setActiveIndex] = useState<number | null>(defaultIndex);
const toggle = (index: number) =>
setActiveIndex((prev) => (prev === index ? null : index));
return (
<AccordionContext.Provider value={{ activeIndex, toggle }}>
<div role="tablist">{children}</div>
</AccordionContext.Provider>
);
}
interface ItemProps {
index: number;
title: string;
children: ReactNode;
}
Accordion.Item = function AccordionItem({ index, title, children }: ItemProps) {
const { activeIndex, toggle } = useAccordion();
const isOpen = activeIndex === index;
return (
<div>
<button role="tab" aria-expanded={isOpen} onClick={() => toggle(index)}>
{title}
</button>
{isOpen && <div role="tabpanel">{children}</div>}
</div>
);
};
使用方式:
<Accordion defaultIndex={0}>
<Accordion.Item index={0} title="Section 1">Content 1</Accordion.Item>
<Accordion.Item index={1} title="Section 2">Content 2</Accordion.Item>
</Accordion>
讓元件可渲染為不同 HTML 元素:
import { type ElementType, type ComponentPropsWithoutRef } from 'react';
type ButtonProps<T extends ElementType = 'button'> = {
as?: T;
variant?: 'primary' | 'secondary';
} & ComponentPropsWithoutRef<T>;
export function Button<T extends ElementType = 'button'>({
as,
variant = 'primary',
className,
...props
}: ButtonProps<T>) {
const Component = as || 'button';
return <Component className={`btn btn-${variant} ${className ?? ''}`} {...props} />;
}
使用方式:
<Button>Click me</Button>
<Button as="a" href="/about">About</Button>
<Button as={Link} to="/home">Home</Button>
同時支援受控與非受控模式:
import { useState, useCallback } from 'react';
interface ToggleProps {
value?: boolean;
defaultValue?: boolean;
onChange?: (value: boolean) => void;
}
export function Toggle({ value, defaultValue = false, onChange }: ToggleProps) {
const [internalValue, setInternalValue] = useState(defaultValue);
const isControlled = value !== undefined;
const currentValue = isControlled ? value : internalValue;
const handleToggle = useCallback(() => {
const next = !currentValue;
if (!isControlled) setInternalValue(next);
onChange?.(next);
}, [currentValue, isControlled, onChange]);
return (
<button
role="switch"
aria-checked={currentValue}
onClick={handleToggle}
>
{currentValue ? 'ON' : 'OFF'}
</button>
);
}
透過 props 傳入不同區域的內容:
import { type ReactNode } from 'react';
interface CardProps {
header?: ReactNode;
footer?: ReactNode;
children: ReactNode;
}
export function Card({ header, footer, children }: CardProps) {
return (
<div className="card">
{header && <div className="card-header">{header}</div>}
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
將渲染邏輯委託給呼叫端:
import { useState, useEffect } from 'react';
interface FetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
interface DataFetcherProps<T> {
url: string;
children: (result: FetchResult<T>) => ReactNode;
}
export function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
const [state, setState] = useState<FetchResult<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then((res) => res.json())
.then((data) => setState({ data, loading: false, error: null }))
.catch((error) => {
if (error.name !== 'AbortError') {
setState({ data: null, loading: false, error });
}
});
return () => controller.abort();
}, [url]);
return <>{children(state)}</>;
}
value + defaultValue 模式tools
基於 SLA/SLO 量化評估事故影響的計算模型與業務影響矩陣。適用於「SLA 影響」、「SLO 違反」、「影響評估」、「營收損失估算」、「Error Budget」、「可用性計算」、「事故成本評估」等量化事故業務影響的任務。強化 impact-assessor 的評估能力。注意:事故原因分析與改善規劃不在此技能範圍內。
research
根因分析(RCA)方法論詳細指南。提供 5 Whys、Fishbone 圖、Fault Tree Analysis、變更分析等結構化 RCA 技術,以及認知偏誤防範清單。適用於「根因分析」、「RCA」、「5 Whys」、「魚骨圖」、「Fault Tree」、「原因分析方法論」、「變更分析」等事故原因分析任務。強化 root-cause-investigator 的分析能力。注意:時間軸重建與改善規劃不在此技能範圍內。
testing
事故事後分析(Postmortem)完整流程。協調 7 個執行階段:資訊收集 → 時間軸重建 → 根因分析 → 影響評估 → 改善規劃 → 報告審查 → 整合報告,最終產出完整的 Postmortem 報告。適用於「寫事故報告」、「post-incident 分析」、「RCA 報告」、「事故時間軸整理」、「建立改善措施」等請求。注意:即時 Incident Response(on-call)、監控系統設定、告警配置不在此技能範圍內。
content-media
投影片版面模式庫。提供 20 種投影片類型的最佳版面配置、格線系統、色彩與字型設計 Token。適用於「投影片版面」、「Slide Layout」、「設計系統」、「格線」、「字型」、「色彩規範」等投影片視覺設計任務。強化 visual-designer 的設計能力。注意:PPT/Keynote 檔案直接輸出不在此技能範圍內。