A/B 测试设计与分析时使用。适用于产品实验、功能验证、转化率优化。融合假设驱动 + 样本量计算 + 置信度判断。
日本語の概要は準備中です。原文の説明を表示しています。
设计组件结构、拆分粒度和复用策略时使用。覆盖 React / Vue / Angular / Svelte。融合 Atomic Design + Container/Presentation + Compound Components + Headless 模式。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
参考来源:Brad Frost《Atomic Design》、Dan Abramov《Presentational and Container Components》、Kent C. Dodds《Compound Components》、Adam Wathan《Inversion of Control》。
1. 单一职责
一个组件做一件事
2. 显式 props,避免 prop drilling
多于 3 层往下传 → 用 Context / 状态管理
3. Composition over Configuration
`<Card><Card.Header /></Card>` 优于
`<Card title="..." footer="..." actions={[...]} />`
4. 数据从上往下,事件从下往上
单向数据流
5. 设计 API 比内部实现重要
API 是契约,能改实现不能改 API
6. 受控 vs 非受控明确
表单组件提供两种模式
7. 可访问性是默认
不是事后补救(详见 a11y skill)
8. 状态最小化
能从 props 派生就不存 state
┌─────────────────────────────────────┐
│ Pages(页面) │
│ 例:UserProfilePage / OrderListPage │
└────────────────┬────────────────────┘
┌────────────────┴────────────────────┐
│ Templates(模板) │
│ 例:DashboardLayout / AuthLayout │
└────────────────┬────────────────────┘
┌────────────────┴────────────────────┐
│ Organisms(生物体) │
│ 例:Header / Sidebar / OrderList │
└────────────────┬────────────────────┘
┌────────────────┴────────────────────┐
│ Molecules(分子) │
│ 例:FormField / SearchBar / UserCard │
└────────────────┬────────────────────┘
┌────────────────┴────────────────────┐
│ Atoms(原子) │
│ 例:Button / Input / Icon / Label │
└─────────────────────────────────────┘
Container(容器):
- 管理状态 / 调用 API / 业务逻辑
- 不写 UI 细节
- 命名:UserListContainer
Presentation(展示):
- 纯 UI / 无状态(或仅 UI 状态)
- 接收 props,emit 事件
- 易测试 / 易复用 / Storybook 友好
- 命名:UserList
// Presentation:纯 UI
interface UserListProps {
users: User[];
isLoading: boolean;
onUserClick: (id: number) => void;
}
export function UserList({ users, isLoading, onUserClick }: UserListProps) {
if (isLoading) return <Skeleton count={5} />;
if (users.length === 0) return <EmptyState />;
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => onUserClick(user.id)}>
{user.name}
</li>
))}
</ul>
);
}
// Container:业务逻辑
export function UserListContainer() {
const { data, isLoading } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
const navigate = useNavigate();
return (
<UserList
users={data ?? []}
isLoading={isLoading}
onUserClick={(id) => navigate(`/users/${id}`)}
/>
);
}
// 用法(用户写)
<Tabs defaultValue="orders">
<Tabs.List>
<Tabs.Trigger value="orders">订单</Tabs.Trigger>
<Tabs.Trigger value="products">商品</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="orders"><OrderList /></Tabs.Panel>
<Tabs.Panel value="products"><ProductList /></Tabs.Panel>
</Tabs>
// 实现:用 Context 共享状态
const TabsContext = createContext<TabsContextValue | null>(null);
export function Tabs({ children, defaultValue }: TabsProps) {
const [value, setValue] = useState(defaultValue);
return (
<TabsContext.Provider value={{ value, setValue }}>
<div data-tabs>{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }) {
return <div role="tablist">{children}</div>;
};
Tabs.Trigger = function TabsTrigger({ value, children }) {
const ctx = useContext(TabsContext);
return (
<button
role="tab"
aria-selected={ctx.value === value}
onClick={() => ctx.setValue(value)}
>
{children}
</button>
);
};
Tabs.Panel = function TabsPanel({ value, children }) {
const ctx = useContext(TabsContext);
return ctx.value === value ? <div role="tabpanel">{children}</div> : null;
}
不提供样式,只提供逻辑和无障碍:
- Radix UI(React)
- Headless UI(React/Vue)
- React Aria
- Melt UI(Svelte)
- Bits UI(Svelte)
- Kobalte(Solid)
优势:
- 100% 自定义样式
- 无障碍由库保证
- 不与设计系统冲突
劣势:
- 需要自己写样式
- 学习曲线
// 用 Radix Dialog
import * as Dialog from '@radix-ui/react-dialog';
export function ConfirmDialog({ children, onConfirm }) {
return (
<Dialog.Root>
<Dialog.Trigger asChild>{children}</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="overlay" />
<Dialog.Content className="content">
<Dialog.Title>确认</Dialog.Title>
<Dialog.Description>...</Dialog.Description>
<button onClick={onConfirm}>确认</button>
<Dialog.Close>取消</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
// 1. 函数组件(推荐)
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', isLoading, children, className, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
disabled={isLoading || props.disabled}
{...props}
>
{isLoading && <Spinner />}
{children}
</button>
);
}
);
Button.displayName = 'Button';
// 2. 自定义 Hook 抽逻辑
function useUserList() {
const [filter, setFilter] = useState('');
const { data, isLoading } = useQuery({
queryKey: ['users', filter],
queryFn: () => fetchUsers({ filter }),
});
return { users: data ?? [], isLoading, filter, setFilter };
}
// 3. memo 避免不必要重渲染(仅当真有性能问题)
export const ExpensiveItem = memo(function ExpensiveItem({ item }) {
return <div>...</div>;
});
<!-- Button.vue -->
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
disabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
</script>
<template>
<button
:class="['btn', `btn-${variant}`, `btn-${size}`]"
:disabled="isLoading || disabled"
@click="emit('click', $event)"
>
<Spinner v-if="isLoading" />
<slot />
</button>
</template>
<!-- 组合式函数(Composables,Vue 的 Hook)-->
<script setup lang="ts">
import { useUserList } from '@/composables/useUserList';
const { users, isLoading, filter } = useUserList();
</script>
<!-- composables/useUserList.ts -->
<script lang="ts">
import { ref, computed } from 'vue';
import { useQuery } from '@tanstack/vue-query';
export function useUserList() {
const filter = ref('');
const { data, isLoading } = useQuery({
queryKey: ['users', filter],
queryFn: () => fetchUsers({ filter: filter.value }),
});
return {
users: computed(() => data.value ?? []),
isLoading,
filter,
};
}
</script>
// button.component.ts
@Component({
selector: 'app-button',
standalone: true,
imports: [CommonModule],
template: `
<button
[class]="'btn btn-' + variant() + ' btn-' + size()"
[disabled]="isLoading() || disabled()"
(click)="onClick.emit($event)"
>
<app-spinner *ngIf="isLoading()" />
<ng-content />
</button>
`,
})
export class ButtonComponent {
variant = input<'primary' | 'secondary'>('primary');
size = input<'sm' | 'md' | 'lg'>('md');
isLoading = input(false);
disabled = input(false);
onClick = output<MouseEvent>();
}
<!-- Button.svelte -->
<script lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
onclick?: (e: MouseEvent) => void;
children: any;
}
let {
variant = 'primary',
size = 'md',
isLoading = false,
onclick,
children,
}: Props = $props();
</script>
<button class="btn btn-{variant} btn-{size}" disabled={isLoading} {onclick}>
{#if isLoading}<Spinner />{/if}
{@render children()}
</button>
推荐:feature-based + 共享组件分离
src/
├── components/ ← 跨业务的通用组件
│ ├── ui/ ← 原子组件(Button, Input, Card)
│ ├── layout/ ← 布局组件
│ └── feedback/ ← Toast, Modal, Skeleton
├── features/ ← 业务模块(领域驱动)
│ ├── orders/
│ │ ├── components/ ← 仅订单用到的组件
│ │ ├── hooks/ ← useOrder, useOrderList
│ │ ├── api/ ← API 调用
│ │ ├── types.ts
│ │ └── OrderListPage.tsx
│ └── products/
│ └── ...
├── pages/ ← 路由页面(薄)
├── lib/ ← 工具函数
└── App.tsx
1. Props 命名
- 布尔:is/has/can 前缀
- 事件:on 前缀(onClick / onSubmit)
- 字符串枚举:明确选项(variant: 'primary' | 'secondary')
2. 默认值
- 提供合理默认
- 不要全部 required
3. 受控 vs 非受控
提供两种模式:
<Input value={x} onChange={fn} /> // 受控
<Input defaultValue="x" /> // 非受控
4. forwardRef
原子组件应该转发 ref
5. ...rest props
原子组件应允许传递 HTML 属性
<Button {...props} />
状态从上往下放:
局部 state(一个组件用) → useState 在该组件
父子共享 → 提到父组件
跨组件 → Context / 状态管理库
服务端数据 → TanStack Query / SWR
不要:
- useState 然后只 setState 一次(用 props)
- useEffect 同步 props 到 state(直接用 props)
- 把 server state 放 Redux(用 React Query)
1. 看设计稿 → 拆分 Atomic Design 五层
↓
2. 列原子组件清单(Button / Input / Icon ...)
↓
3. 看是否复用现有组件库(不要重造轮子)
↓
4. 组件 API 设计(先 README 再写代码)
↓
5. 实现 + Storybook story
↓
6. 单元测试 + 集成测试 + 视觉测试
↓
7. 检查 a11y(键盘 + 屏幕阅读器)
↓
8. 性能(memo 必要时 / 懒加载)
↓
9. 文档(API + 用法示例)
templates/component-design-template.md — 组件设计文档(API + 状态 + 复用 + 测试 + a11y)□ 单一职责(一个组件做一件事)
□ Props 类型完整(TypeScript)
□ 默认值合理
□ 受控 / 非受控明确
□ forwardRef(原子组件)
□ ...rest 透传
□ a11y 完整(角色 / 键盘 / 屏幕阅读器)
□ 状态最小化
□ 命名清晰
□ Storybook story 覆盖主要状态
□ 测试覆盖
□ 不深嵌套(< 3 层 prop drilling)
□ 不滥用 memo / useCallback
□ Loading / Empty / Error 状态完整
上游:
ui-ux-designer 工作流 → 设计稿 / 状态 / 流程
api-designer → API 契约(影响数据组件)
下游:
state-management → 全局状态
styling-system → 样式
forms-validation → 表单组件
data-fetching → 数据组件
testing-frontend → 测试覆盖
accessibility-implementation → a11y 实现
references/frontend-component-libraries.md — 组件库选型references/frontend-frameworks-2026.md — 框架对比まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
A/B 测试设计与分析时使用。适用于产品实验、功能验证、转化率优化。融合假设驱动 + 样本量计算 + 置信度判断。
日本語の概要は準備中です。原文の説明を表示しています。
上线前业务方/产品方验收时使用。适用于功能交付确认、UAT、发布会签。融合 BDD Given-When-Then、Specification by Example、ATDD。
日本語の概要は準備中です。原文の説明を表示しています。
设计无障碍可访问的界面时使用。适用于满足 WCAG 标准、键盘导航、屏幕阅读器支持。优先使用 WCAG 2.2 AA 级标准 + 键盘 + 焦点 + 对比度 + ARIA。
日本語の概要は準備中です。原文の説明を表示しています。
实现可访问性(无障碍)时使用。覆盖 WCAG 2.2 AA / ARIA / 键盘导航 / 屏幕阅读器 / 颜色对比 / 焦点管理。融合 React Aria / Radix UI / Headless UI 实践 + axe-core 自动化测试。
日本語の概要は準備中です。原文の説明を表示しています。
AI 模型部署时使用。适用于推理服务 / GPU / TensorRT / 监控 / 扩缩容。
日本語の概要は準備中です。原文の説明を表示しています。
数据分析报告输出时使用。适用于业务分析报告、周报、专项分析、决策支持。融合洞察 + 建议 + 行动项。
日本語の概要は準備中です。原文の説明を表示しています。