元宝

Next.js 组件开发最佳实践文档

(基于模块化、TypeScript、UI/状态/操作分离设计)


目录结构规范

bash 复制代码
components/
├─ search/                  # 功能模块目录(如搜索相关组件)
│  ├─ hooks/               # 模块专属自定义 Hook
│  │  └─ use-search.ts     
│  ├─ agent-card/          # 单个组件独立目录
│  │  ├─ agent-card.tsx    # 主组件(UI + 接口定义)
│  │  ├─ agent-card.stories.tsx  # Storybook 交互文档
│  │  ├─ agent-card.test.tsx     # 单元测试
│  │  └─ index.ts          # 统一导出
│  └─ ...                  
├─ ui/                     # 基础 UI 组件库
│  ├─ prompt-input/        # 带封装逻辑的 UI 组件
│  │  ├─ prompt-input.tsx  
│  │  ├─ prompt-input.stories.tsx
│  │  └─ index.ts
│  └─ ...
└─ ...

组件设计原则

1. 类型定义先行(TypeScript First)

typescript 复制代码
// components/search/agent-card/agent-card.tsx
interface AgentData {
  id: string;
  name: string;
  status: 'online' | 'offline';
}

type AgentCardProps = {
  agent: AgentData;
  onSelect?: (id: string) => void;
  className?: string;
};

2. UI 与逻辑分离

tsx 复制代码
// components/search/agent-card/agent-card.tsx
export const AgentCardUI = ({ 
  name, 
  status, 
  onSelect 
}: Pick<AgentCardProps, 'name' | 'status' | 'onSelect'>) => {
  return (
    <div className={styles.card} onClick={() => onSelect?.()}>
      <Avatar name={name} />
      <StatusBadge status={status} />
    </div>
  );
};

export const AgentCard = ({ agent, ...props }: AgentCardProps) => {
  // 逻辑处理层(数据转换/状态管理)
  const { isHighlighted } = useAgentHighlights(agent.id);
  
  return (
    <AgentCardUI 
      {...agent} 
      {...props}
      className={clsx(isHighlighted && styles.highlight)}
    />
  );
};

3. 状态管理策略

组件状态 :使用 useStateuseReducer 管理局部状态

跨组件状态:通过 Context + Custom Hook 封装

typescript 复制代码
// components/search/hooks/use-search.ts
export const useSearch = () => {
  const [keywords, setKeywords] = useState('');
  const { data, isLoading } = useSWR(`/api/search?q=${keywords}`, fetcher);

  return {
    keywords,
    setKeywords,
    results: data,
    isLoading
  };
};

4. 操作行为封装

• 事件处理函数独立为纯函数

• 异步操作使用 Service 层抽象

typescript 复制代码
// components/search/agent-card/actions.ts
export const fetchAgentDetails = async (id: string) => {
  const response = await fetch(`/api/agents/${id}`);
  return response.json();
};

// 在组件中使用
const handleSelect = async (id: string) => {
  const details = await fetchAgentDetails(id);
  // ...更新状态
};

文档与测试规范

1. Storybook 交互文档

typescript 复制代码
// components/search/agent-card/agent-card.stories.tsx
const meta = {
  title: 'Search/AgentCard',
  component: AgentCard,
  tags: ['autodocs'],
} satisfies Meta<typeof AgentCard>;

export default meta;

export const OnlineAgent = {
  args: {
    agent: {
      id: '1',
      name: 'John Doe',
      status: 'online'
    }
  }
};

export const OfflineAgent = {
  args: {
    ...OnlineAgent.args,
    agent: { ...OnlineAgent.args.agent, status: 'offline' }
  }
};

2. 单元测试规范

typescript 复制代码
// components/search/agent-card/agent-card.test.tsx
describe('AgentCard', () => {
  it('显示在线状态徽章', () => {
    const { getByText } = render(
      <AgentCard agent={{ id: '1', name: 'Test', status: 'online' }} />
    );
    expect(getByText('online')).toBeInTheDocument();
  });

  it('点击触发选择事件', async () => {
    const mockSelect = vi.fn();
    const { container } = render(
      <AgentCard agent={mockAgent} onSelect={mockSelect} />
    );
    await user.click(container.firstChild!);
    expect(mockSelect).toHaveBeenCalledWith('1');
  });
});

代码组织最佳实践

1. 模块化入口文件

typescript 复制代码
// components/search/agent-card/index.ts
export { AgentCard } from './agent-card';
export type { AgentData, AgentCardProps } from './agent-card';

2. 样式隔离方案

• 使用 CSS Modules 或 Styled Components

• 避免全局样式污染

scss 复制代码
// components/search/agent-card/agent-card.module.scss
.card {
  border: 1px solid var(--neutral-200);
  
  &.highlight {
    border-color: var(--blue-500);
  }
}

3. 组件 Props 设计规范

• 必填属性不加 ? 修饰符

• 事件处理器统一以 on 前缀命名

• 样式扩展使用 className + clsx 组合


总结:核心优势

维度 实现方案
可维护性 模块化目录结构 + 单一职责组件
可测试性 Storybook 可视化 + Jest 单元测试
扩展性 接口驱动设计 + 组合式 Props
类型安全 严格 TypeScript 类型定义
协作效率 自描述组件 + 交互式文档

实施参考 :根据图中 agent-card.tsx 结构,将 UI 渲染与业务逻辑解耦,通过 Storybook 实现可视化调试,结合 TypeScript 接口确保类型安全。

相关推荐
Anlici27 分钟前
跨域解决方案还有优劣!?
前端·面试
庸俗今天不摸鱼33 分钟前
【万字总结】构建现代Web应用的全方位性能优化体系学习指南(二)
前端·性能优化·webp
追寻光1 小时前
Java 绘制图形验证码
java·前端
前端snow1 小时前
爬取数据利用node也行,你知道吗?
前端·javascript·后端
村头一颗草1 小时前
高德爬取瓦片和vue2使用
前端·javascript·vue.js
远山无期1 小时前
vue3+vite项目接入qiankun微前端关键点
前端·vue.js
陈随易1 小时前
告别Node.js:2025年,我为何全面拥抱Bun
前端·后端·程序员
还是鼠鼠1 小时前
Node.js--exports 对象详解:用法、示例与最佳实践
前端·javascript·vscode·node.js·web
CQU_JIAKE1 小时前
2.5[frontEnd]
前端
Moment1 小时前
前端性能指标 —— FMP
前端·javascript·面试