元宝

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

相关推荐
百万蹄蹄向前冲9 分钟前
组建百万前端梦之队-计算机大学生竞赛发展蓝图
前端·vue.js·掘金社区
云隙阳光i22 分钟前
实现手机手势签字功能
前端·javascript·vue.js
imkaifan42 分钟前
vue2升级Vue3--native、对inheritAttrs作用做以解释、声明的prop属性和未声明prop的属性
前端·vue.js·native修饰符·inheritattrs作用·声明的prop属性·未声明prop的属性
觉醒法师44 分钟前
HarmonyOS NEXT - 电商App实例三( 网络请求axios)
前端·华为·typescript·axios·harmonyos·ark-ts
Danta1 小时前
HTTP协议版本演进:从HTTP/0.9到HTTP/3的高分面试回答
前端·网络协议·面试
柠檬树^-^2 小时前
app.config.globalProperties
前端·javascript·vue.js
太阳花ˉ2 小时前
react(一):特点-基本使用-JSX语法
前端·react.js
赵大仁2 小时前
深入解析 React Diff 算法:原理、优化与实践
前端·react.js·前端框架
1024小神2 小时前
vue/react/vite前端项目打包的时候加上时间最简单版本,防止后端扯皮
前端·vue.js·react.js
轻口味3 小时前
Vue.js 与 RESTful API 集成之使用 Axios 请求数据
前端·vue.js·restful