元宝

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 接口确保类型安全。

相关推荐
非ban必选20 小时前
netty-scoket.io路径配置
java·服务器·前端
じòぴé南冸じょうげん21 小时前
小程序的project.private.config.json是无依赖文件,那可以删除吗?
前端·小程序·json
会豪21 小时前
Electron主进程渲染进程如何优雅的进行通信
前端
jianghaha201121 小时前
前端 Word 模板参入特定数据 并且下载
前端·word
跟橙姐学代码21 小时前
轻松搞定 Python 模块与包导入:新手也能秒懂的入门指南
前端·python·ipython
aiwery21 小时前
大模型场景下的推送技术选型:轮询 vs WebSocket vs SSE
前端·agent
会豪21 小时前
前端插件-不固定高度的DIV如何增加transition
前端
却尘21 小时前
Server Actions 深度剖析(2):缓存管理与重新验证,如何用一行代码干掉整个客户端状态层
前端·客户端·next.js
小菜全21 小时前
Vue 3 + TypeScript 事件触发与数据绑定方法
前端·javascript·vue.js
Hilaku21 小时前
面试官开始问我AI了,前端的危机真的来了吗?
前端·javascript·面试