mcpField
F
Feature-Sliced Design

Feature-Sliced Design

github.com/constellos开发0 次安装
代码仓库

关于 Feature-Sliced Design

Feature-Sliced Design 是面向前端团队的架构方法技能,通过分层、切片和分段组织可扩展应用。其自定义 `views` 层将页面逻辑与 Next.js App Router 路由分开。

主要功能

  1. 01七层架构组织代码
  2. 02按层级限制导入方向
  3. 03按领域划分切片与公共 API
  4. 04按用途划分代码段
  5. 05Next.js App Router 组织方式
  6. 06Turborepo 应用结构指导

适用场景

  1. 01搭建新的 Next.js 应用
  2. 02重构不断扩大的前端代码库
  3. 03协调团队并行开发
  4. 04组织 Turborepo 中的独立应用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/constellos/claude-code --skill feature-sliced-design

安装后在 Claude Code 中输入/feature-sliced-design确认已加载。

详细介绍

Feature-Sliced Design

目的

Feature-Sliced Design(FSD)是一种架构方法,用于将前端应用组织成标准化、可扩展的结构。它通过分层结构清晰划分关注点,避免循环依赖并提升可维护性。

使用 FSD 的原因:

  • 可扩展性:随着应用扩展而自然成长
  • 可维护性:清晰的边界让重构更安全
  • 团队协作:一致的结构便于并行开发
  • 入职培训:新开发者能快速理解架构

自定义的 views 层: 此技能使用 views 代替标准 FSD 的 pages 层,以避免与 Next.js App Router 的 /app 目录混淆。/app 目录仅负责路由(逻辑尽可能精简),而 /src/views 包含实际的页面业务逻辑。

Next.js 集成: FSD 与 Next.js App Router 可无缝协作,将路由相关逻辑(位于 /app)与业务逻辑(位于 /src/views 及其他 FSD 层)分开。这样既能保持路由配置简洁,又能保留 FSD 的架构优势。

适用场景

以下情况可采用 Feature-Sliced Design:

  • 启动需要清晰架构边界的新 Next.js 项目
  • 重构缺乏一致结构、不断扩大的代码库
  • 与需要标准化组织方式的多开发者团队协作
  • 构建业务逻辑复杂、需要划分关注点的应用
  • 开发 Turborepo 单体仓库应用,其中每个应用都需要独立的 FSD 结构
  • 扩展存在循环依赖问题的应用
  • 创建需要长期维护的企业级应用

核心原则

层级结构

FSD 将代码组织为 7 个标准化层(从高到低):

  1. app - 应用初始化、全局提供程序、路由配置
  2. processes - 已弃用(功能已移至 features 和 app)
  3. views - 页面级业务逻辑(自定义命名,替代标准的 pages)
  4. widgets - 跨越多个功能的大型组合式 UI 区块
  5. features - 具有业务价值的面向用户交互
  6. entities - 业务领域对象和模型
  7. shared - 可复用工具、UI 套件、第三方集成

导入规则: 模块只能从层级中严格低于自身的层导入。

┌─────────────────┐
│      app        │  ← Can import from all layers below
├─────────────────┤
│     views       │  ← Can import: widgets, features, entities, shared
├─────────────────┤
│    widgets      │  ← Can import: features, entities, shared
├─────────────────┤
│    features     │  ← Can import: entities, shared
├─────────────────┤
│    entities     │  ← Can import: shared only
├─────────────────┤
│     shared      │  ← Cannot import from any FSD layer
└─────────────────┘

此层级结构可避免循环依赖,并确保架构边界清晰。

views 层与 pages 层

使用 views 而非 pages 的原因:

  • Next.js 使用 /app 目录进行路由(App Router)
  • 标准 FSD 使用 pages 层来存放页面业务逻辑
  • 使用 views 可避免混淆路由(/app)与业务逻辑(/src/views)

关注点分离:

  • /app 目录(根级别):仅处理 Next.js 路由,逻辑尽可能精简
    • 包含 page.tsx、layout.tsx、路由组
    • 从 /src/views 导入并渲染内容
  • /src/views 层(FSD):页面业务逻辑、组件组合
    • 包含视图组件、模型、API 调用
    • 组合 widgets、features、entities

这种分离既能保持路由配置简洁,又能遵循 FSD 的架构原则。

切片

切片是在各层内部按领域划分的分区(app 和 shared 除外,因为它们没有切片)。

示例:

  • views/dashboard - 仪表盘页面切片
  • widgets/header - 页眉组件切片
  • features/auth - 身份验证功能切片
  • entities/user - 用户实体切片

公共 API 模式: 每个切片都通过 index.ts 导出,以控制其公共接口:

// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';
// Internal implementation details NOT exported

这样可以避免深层导入并维持封装性。

切片

切片是切片内基于用途的分组:

  • ui/ - React 组件、视觉元素
  • model/ - 业务逻辑、状态管理、TypeScript 类型
  • api/ - API 客户端、数据获取、外部集成
  • lib/ - 切片专用的实用函数、辅助工具
  • config/ - 配置常量、功能标志

结构示例:

features/
└── auth/
    ├── ui/
    │   ├── LoginForm.tsx
    │   └── SignupForm.tsx
    ├── model/
    │   ├── useAuth.ts
    │   └── types.ts
    ├── api/
    │   └── authApi.ts
    └── index.ts

使用 Next.js App Router 的 FSD

路由架构

Next.js App Router 使用 /app 目录进行路由。FSD 层位于 /src 目录中。

文件组织:

my-nextjs-app/
├── app/                          # Next.js routing (minimal logic)
│   ├── layout.tsx               # Root layout
│   ├── page.tsx                 # Home route
│   ├── dashboard/
│   │   └── page.tsx            # Dashboard route
│   └── settings/
│       └── page.tsx            # Settings route
│
├── src/                         # FSD layers
│   └── views/                  # Page business logic
│       ├── home/
│       │   ├── ui/
│       │   │   └── HomeView.tsx
│       │   └── index.ts
│       ├── dashboard/
│       │   ├── ui/
│       │   │   └── DashboardView.tsx
│       │   ├── model/
│       │   │   └── useDashboard.ts
│       │   └── index.ts
│       └── settings/
│           ├── ui/
│           │   └── SettingsView.tsx
│           └── index.ts

路由页面从视图中导入:

// app/dashboard/page.tsx - Routing only
import { DashboardView } from '@/views/dashboard';

export default function DashboardPage() {
  return <DashboardView />;
}

// src/views/dashboard/ui/DashboardView.tsx - Business logic
import { Header } from '@/widgets/header';
import { StatsCard } from '@/features/analytics';

export function DashboardView() {
  return (
    <div>
      <Header />
      <StatsCard />
    </div>
  );
}
独立的 Next.js 结构

独立 Next.js 应用的完整 FSD 结构:

my-nextjs-app/
├── app/                          # Next.js App Router
│   ├── layout.tsx               # Root layout
│   ├── page.tsx                 # Home route
│   ├── (auth)/                  # Route group
│   │   ├── login/
│   │   │   └── page.tsx
│   │   └── signup/
│   │       └── page.tsx
│   ├── dashboard/
│   │   └── page.tsx
│   ├── api/                     # API routes
│   │   └── users/
│   │       └── route.ts
│   └── not-found.tsx
│
├── src/
│   ├── app/                     # App layer (no slices)
│   │   ├── providers/
│   │   │   ├── AuthProvider.tsx
│   │   │   └── QueryProvider.tsx
│   │   ├── styles/
│   │   │   └── globals.css
│   │   └── config/
│   │       └── constants.ts
│   │
│   ├── views/                   # Views layer (page logic)
│   │   ├── home/
│   │   ├── dashboard/
│   │   ├── login/
│   │   └── signup/
│   │
│   ├── widgets/                 # Widgets layer
│   │   ├── header/
│   │   ├── sidebar/
│   │   ├── footer/
│   │   └── notification-panel/
│   │
│   ├── features/                # Features layer
│   │   ├── auth/
│   │   ├── search/
│   │   ├── theme-toggle/
│   │   └── user-profile/
│   │
│   ├── entities/                # Entities layer
│   │   ├── user/
│   │   ├── post/
│   │   ├── comment/
│   │   └── session/
│   │
│   └── shared/                  # Shared layer (no slices)
│       ├── ui/                  # UI components
│       │   ├── button/
│       │   ├── input/
│       │   └── card/
│       ├── lib/                 # Utilities
│       │   ├── format.ts
│       │   └── validation.ts
│       ├── api/                 # API client
│       │   └── client.ts
│       └── config/
│           └── env.ts
│
├── public/
│   ├── images/
│   └── fonts/
│
└── package.json
Turborepo 单仓库结构

Turborepo 单仓库中的 FSD 结构,其中每个应用都有独立的 FSD 组织结构:

turborepo-root/
├── apps/
│   ├── web/                     # Consumer-facing app
│   │   ├── app/                # Next.js routing
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx
│   │   │   └── shop/
│   │   │       └── page.tsx
│   │   ├── src/                # Independent FSD structure
│   │   │   ├── app/
│   │   │   ├── views/
│   │   │   │   ├── home/
│   │   │   │   └── shop/
│   │   │   ├── widgets/
│   │   │   │   ├── product-grid/
│   │   │   │   └── shopping-cart/
│   │   │   ├── features/
│   │   │   │   ├── add-to-cart/
│   │   │   │   └── checkout/
│   │   │   ├── entities/
│   │   │   │   ├── product/
│   │   │   │   └── order/
│   │   │   └── shared/
│   │   └── package.json
│   │
│   └── admin/                   # Admin dashboard app
│       ├── app/                # Next.js routing
│       │   ├── layout.tsx
│       │   ├── page.tsx
│       │   └── products/
│       │       └── page.tsx
│       ├── src/                # Independent FSD structure
│       │   ├── app/
│       │   ├── views/
│       │   │   ├── dashboard/
│       │   │   └── products/
│       │   ├── widgets/
│       │   │   ├── admin-header/
│       │   │   └── stats-panel/
│       │   ├── features/
│       │   │   ├── product-editor/
│       │   │   └── user-management/
│       │   ├── entities/
│       │   │   ├── product/
│       │   │   └── admin/
│       │   └── shared/
│       └── package.json
│
├── packages/                    # Optional shared packages
│   ├── ui/                     # Shared UI components (can mirror shared/ui)
│   │   ├── button/
│   │   └── input/
│   ├── utils/                  # Shared utilities
│   │   └── validation.ts
│   └── types/                  # Shared TypeScript types
│       └── common.ts
│
├── turbo.json
└── package.json

Turborepo 的主要原则:

  • 每个应用(web、admin)都有自己的完整 FSD 结构
  • 应用彼此独立,不从 FSD 层跨应用导入
  • 共享代码放在 packages/ 目录中(可选)
  • 使用 workspace:* 协议作为包依赖

层定义

app 层

用途: 应用级设置、初始化和全局配置。

职责:

  • 全局提供程序(主题、身份验证、查询客户端)
  • 根样式和 CSS 导入
  • 应用级配置
  • 错误边界

导入规则: 可以从所有下层导入(views、widgets、features、entities、shared)。

无切片: app 层直接包含各个分段(providers/、styles/、config/)。

示例:

// src/app/providers/Providers.tsx
'use client';

import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/shared/api/queryClient';
import { AuthProvider } from '@/features/auth';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        {children}
      </AuthProvider>
    </QueryClientProvider>
  );
}

// app/layout.tsx
import { Providers } from '@/app/providers/Providers';
import '@/app/styles/globals.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
views 层

用途: 页面级业务逻辑和组件组合。

职责:

  • 将 widgets、features 和 entities 组合成完整页面
  • 页面专属的状态管理
  • 页面的数据获取
  • SEO 元数据

导入规则: 可以从 widgets、features、entities、shared 导入。

包含切片: 每个页面都有自己的切片(例如,views/dashboard、views/settings)。

示例:

// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header';
import { Sidebar } from '@/widgets/sidebar';
import { StatsCard } from '@/features/analytics';
import { RecentActivity } from '@/features/activity';
import { User } from '@/entities/user';

interface DashboardViewProps {
  user: User;
}

export function DashboardView({ user }: DashboardViewProps) {
  return (
    <div className="dashboard">
      <Header user={user} />
      <div className="dashboard-content">
        <Sidebar />
        <main>
          <StatsCard userId={user.id} />
          <RecentActivity userId={user.id} />
        </main>
      </div>
    </div>
  );
}

// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';

// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getCurrentUser } from '@/entities/user';

export default async function DashboardPage() {
  const user = await getCurrentUser();
  return <DashboardView user={user} />;
}
widgets 层

用途: 大型、自包含的复合 UI 区块,组合多个功能。

职责:

  • 可在多个页面间复用
  • 组合多个功能
  • 复杂 UI 布局(页眉、侧边栏、页脚)
  • 导航组件

导入规则: 可以从 features、entities、shared 导入。

包含切片: 每个 widget 都有自己的切片(例如,widgets/header、widgets/sidebar)。

示例:

// src/widgets/header/ui/Header.tsx
import { SearchBar } from '@/features/search';
import { UserMenu } from '@/features/user-menu';
import { NotificationBell } from '@/features/notifications';
import { User } from '@/entities/user';
import { Logo } from '@/shared/ui/logo';

interface HeaderProps {
  user: User;
}

export function Header({ user }: HeaderProps) {
  return (
    <header className="header">
      <Logo />
      <SearchBar />
      <div className="header-actions">
        <NotificationBell userId={user.id} />
        <UserMenu user={user} />
      </div>
    </header>
  );
}

// src/widgets/header/index.ts
export { Header } from './ui/Header';
features 层

用途: 具有明确业务价值、面向用户的交互和业务逻辑。

职责:

  • 特定用户操作(登录、加入购物车、点赞帖子)
  • 功能专属的状态管理
  • 业务逻辑和验证
  • 功能的 API 交互

导入规则: 可以从 entities、shared 导入。

包含切片: 每个功能都有自己的切片(例如,features/auth、features/search)。

示例:

// src/features/auth/model/types.ts
export interface LoginCredentials {
  email: string;
  password: string;
}

// src/features/auth/api/login.ts
import { User } from '@/entities/user';
import { apiClient } from '@/shared/api/client';
import type { LoginCredentials } from '../model/types';

export async function login(credentials: LoginCredentials): Promise<User> {
  const response = await apiClient.post('/auth/login', credentials);
  return response.data;
}

// src/features/auth/ui/LoginForm.tsx
'use client';

import { useState } from 'react';
import { login } from '../api/login';
import { Button } from '@/shared/ui/button';
import { Input } from '@/shared/ui/input';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await login({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <Input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <Input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <Button type="submit">Login</Button>
    </form>
  );
}

// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { login } from './api/login';
export type { LoginCredentials } from './model/types';
entities 层

用途: 业务领域对象和核心数据模型。

职责:

  • 表示业务概念的数据结构
  • 实体专用的实用工具
  • 基础 API 操作(CRUD)
  • 类型定义

导入规则: 只能从 shared 导入。

包含切片: 每个实体都有自己的切片(例如,entities/user、entities/post)。

示例:

// src/entities/user/model/types.ts
export interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  role: 'admin' | 'user';
}

// src/entities/user/api/getUser.ts
import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';

export async function getUser(id: string): Promise<User> {
  const response = await apiClient.get(`/users/${id}`);
  return response.data;
}

export async function getCurrentUser(): Promise<User> {
  const response = await apiClient.get('/users/me');
  return response.data;
}

// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';
import { Avatar } from '@/shared/ui/avatar';

interface UserCardProps {
  user: User;
}

export function UserCard({ user }: UserCardProps) {
  return (
    <div className="user-card">
      <Avatar src={user.avatar} alt={user.name} />
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
      </div>
    </div>
  );
}

// src/entities/user/index.ts
export { UserCard } from './ui/UserCard';
export { getUser, getCurrentUser } from './api/getUser';
export type { User } from './model/types';
shared 层

用途: 可复用的实用工具、UI 组件和第三方集成。

职责:

  • UI 套件(按钮、输入框、卡片组件)
  • 辅助函数(格式化工具、验证器)
  • API 客户端配置
  • 常量和环境变量
  • 第三方库集成

导入规则: 不能从任何 FSD 层导入(只能导入外部包)。

无切片: 直接包含各个分段(ui/、lib/、api/、config/)。

示例:

// src/shared/ui/button/Button.tsx
import { type ButtonHTMLAttributes } from 'react';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
}

export function Button({
  variant = 'primary',
  size = 'md',
  className,
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={`button button--${variant} button--${size} ${className}`}
      {...props}
    >
      {children}
    </button>
  );
}

// src/shared/lib/format.ts
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US').format(date);
}

export function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(amount);
}

// src/shared/api/client.ts
import axios from 'axios';

export const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// src/shared/config/env.ts
export const env = {
  apiUrl: process.env.NEXT_PUBLIC_API_URL!,
  nodeEnv: process.env.NODE_ENV,
} as const;

工作流

步骤 1:设置层目录

创建 FSD 文件夹结构:

mkdir -p src/{app,views,widgets,features,entities,shared}
mkdir -p src/app/{providers,styles,config}
mkdir -p src/shared/{ui,lib,api,config}
步骤 2:创建第一个实体

从实体(底层)开始。定义核心业务模型:

// src/entities/user/model/types.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

// src/entities/user/api/getUser.ts
export async function getUser(id: string): Promise<User> {
  // API implementation
}

// src/entities/user/index.ts
export type { User } from './model/types';
export { getUser } from './api/getUser';
步骤 3:使用实体构建功能

创建使用实体的功能:

// src/features/user-profile/ui/UserProfile.tsx
import { User } from '@/entities/user'; // ✅ Feature imports entity

export function UserProfile({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

// src/features/user-profile/index.ts
export { UserProfile } from './ui/UserProfile';
步骤 4:从功能组合组件

构建组合式组件:

// src/widgets/header/ui/Header.tsx
import { UserProfile } from '@/features/user-profile'; // ✅ Widget imports feature
import { SearchBar } from '@/features/search';

export function Header({ user }) {
  return (
    <header>
      <SearchBar />
      <UserProfile user={user} />
    </header>
  );
}
步骤 5:组装视图

创建页面级视图:

// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header'; // ✅ View imports widget

export function DashboardView() {
  return (
    <div>
      <Header />
      {/* More content */}
    </div>
  );
}

// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';
步骤 6:连接到应用路由器

将视图接入 Next.js 路由:

// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';

export default function DashboardPage() {
  return <DashboardView />;
}

导入规则和依赖关系

允许的导入模式
// ✅ Layer importing from layer below
import { User } from '@/entities/user';          // Feature → Entity
import { LoginForm } from '@/features/auth';     // Widget → Feature
import { Header } from '@/widgets/header';       // View → Widget

// ✅ Any layer importing from shared
import { Button } from '@/shared/ui/button';
import { formatDate } from '@/shared/lib/format';

// ✅ Slice importing from different slice in lower layer
import { User } from '@/entities/user';          // features/auth → entities/user
import { Post } from '@/entities/post';          // features/like → entities/post
禁止的导入模式
// ❌ Layer importing from same or higher layer
import { DashboardView } from '@/views/dashboard';  // Feature → View (upward)
import { Header } from '@/widgets/header';          // Feature → Widget (upward)
import { LoginForm } from '@/features/login';       // features/auth → features/login (same layer)

// ❌ Cross-slice imports within same layer
import { SearchBar } from '@/features/search';      // features/auth → features/search

// ❌ Shared importing from FSD layers
import { User } from '@/entities/user';             // shared/lib → entities/user
有效与无效示例

无效(跨功能导入):

// ❌ src/features/search/ui/SearchBar.tsx
import { LoginForm } from '@/features/auth'; // Same layer import

有效(提取到组件):

// ✅ src/widgets/navbar/ui/Navbar.tsx
import { SearchBar } from '@/features/search';
import { LoginForm } from '@/features/auth';

export function Navbar() {
  return (
    <nav>
      <SearchBar />
      <LoginForm />
    </nav>
  );
}
修复循环依赖

问题:

// features/auth imports features/user-settings
// features/user-settings imports features/auth
// ❌ Circular dependency

解决方案 1:提取到实体

// Move shared logic to entities/user
// Both features import from entities/user
// ✅ No circular dependency

解决方案 2:提取到组件

// Create widgets/user-panel that imports both features
// ✅ Widget layer can import from features
强制执行公共 API

始终使用 index.ts 控制导出:

// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';

// ❌ Do NOT export internal helpers
// export { validatePassword } from './lib/validation'; // Keep internal

仅从公共 API 导入:

// ✅ Correct
import { LoginForm } from '@/features/auth';

// ❌ Wrong (deep import)
import { LoginForm } from '@/features/auth/ui/LoginForm';

分段模式

ui/ 分段

用途: React 组件和视觉元素。

适用场景:

  • 任何 React 组件
  • UI 组合
  • 视觉呈现

示例:

// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';

export function UserCard({ user }: { user: User }) {
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}
model/ 分段

用途: 业务逻辑、状态管理和类型定义。

适用场景:

  • TypeScript 接口和类型
  • 用于管理状态的 React 钩子
  • 业务逻辑函数
  • 数据转换

示例:

// src/features/auth/model/useAuth.ts
'use client';

import { create } from 'zustand';
import type { User } from '@/entities/user';

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  login: (user: User) => void;
  logout: () => void;
}

export const useAuth = create<AuthState>((set) => ({
  user: null,
  isAuthenticated: false,
  login: (user) => set({ user, isAuthenticated: true }),
  logout: () => set({ user: null, isAuthenticated: false }),
}));
api/ 分段

用途: API 客户端、数据获取和外部集成。

适用场景:

  • HTTP 请求
  • WebSocket 连接
  • 第三方 API 集成
  • 服务端操作(Next.js)

示例:

// src/entities/user/api/userApi.ts
'use server';

import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';

export async function fetchUsers(): Promise<User[]> {
  const response = await apiClient.get('/users');
  return response.data;
}

export async function createUser(data: Omit<User, 'id'>): Promise<User> {
  const response = await apiClient.post('/users', data);
  return response.data;
}
lib/ 分段

用途: 专用于切片的工具函数和辅助函数。

适用场景:

  • 切片专用工具
  • 验证函数
  • 数据转换辅助函数

示例:

// src/features/auth/lib/validation.ts
import { z } from 'zod';

export const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

export function validateLogin(data: unknown) {
  return loginSchema.parse(data);
}
config/ 分段

用途: 配置常量和功能标志。

适用场景:

  • 功能专用常量
  • 配置对象
  • 功能标志

示例:

// src/app/config/theme.ts
export const theme = {
  colors: {
    primary: '#0070f3',
    secondary: '#ff4081',
  },
  breakpoints: {
    sm: '640px',
    md: '768px',
    lg: '1024px',
  },
} as const;

迁移策略

将现有 Next.js 应用迁移到 FSD

自底向上的方法(推荐):

  1. 从共享层开始

    • 将通用 UI 组件提取到 shared/ui/
    • 将工具函数移至 shared/lib/
    • 在 shared/api/ 中配置 API 客户端
  2. 定义实体

    • 确定业务领域对象(User、Post、Comment)
    • 在 entities/{name}/model/ 中创建实体类型
    • 将 CRUD 操作移至 entities/{name}/api/
  3. 提取功能

    • 确定用户交互(登录、搜索、点赞)
    • 在 features/{name}/ 中创建功能切片
    • 在功能中使用实体
  4. 构建组件

    • 确定组合式 UI 区块(Header、Sidebar)
    • 在 widgets/{name}/ 中创建组件切片
    • 在组件中组合功能
  5. 组织视图

    • 将页面逻辑从 /app 移至 /src/views
    • 将路由保留在 /app,将业务逻辑放在 /src/views
    • 在视图中组合组件
  6. 配置应用层

    • 将全局提供程序移至 app/providers/
    • 将全局样式移至 app/styles/
处理现有代码

渐进式迁移:

  • 每次迁移一个页面
  • 从最简单的页面开始
  • 过渡期间同时使用新旧结构
  • 迁移时更新导入

全程测试:

  • 每迁移一层后运行测试
  • 确保功能未受影响
  • 验证导入路径正确

最佳实践

保持切片隔离:

  • 同一层内的切片之间不要相互导入
  • 每个切片都应保持独立
  • 将共享逻辑提取到较低层

采用公共 API 模式:

  • 始终通过 index.ts 导出
  • 避免深层导入
  • 便于重构

将测试与代码放在一起:

features/
└── auth/
    ├── ui/
    │   ├── LoginForm.tsx
    │   └── LoginForm.test.tsx  # Test next to implementation
    └── index.ts

避免“巨型切片”:

  • 让切片专注于单一职责
  • 将大型切片拆分成多个较小的切片
  • 将通用逻辑提取到共享层

按领域而非技术命名:

  • ✅ features/product-search
  • ❌ features/search-bar-component

使用 TypeScript 严格模式:

{
  "compilerOptions": {
    "strict": true
  }
}

记录架构决策:

  • 保留 ADR(架构决策记录)
  • 记录某些切片存在的原因
  • 说明层边界的决策

常见模式

共享 UI 组件
// src/shared/ui/button/Button.tsx
export function Button({ children, ...props }) {
  return <button {...props}>{children}</button>;
}

// Usage in feature
import { Button } from '@/shared/ui/button';
API 客户端设置
// src/shared/api/client.ts
import axios from 'axios';

export const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
});

// Usage in entity
import { apiClient } from '@/shared/api/client';
使用功能处理表单
// src/features/product-form/ui/ProductForm.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { productSchema } from '../lib/validation';
import { createProduct } from '../api/createProduct';

export function ProductForm() {
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(productSchema),
  });

  return <form onSubmit={handleSubmit(createProduct)}>...</form>;
}
身份验证集成
// src/features/auth/model/useAuth.ts
export const useAuth = create<AuthState>((set) => ({...}));

// src/widgets/header/ui/Header.tsx
import { useAuth } from '@/features/auth';

export function Header() {
  const { user } = useAuth();
  return <header>Welcome, {user?.name}</header>;
}
使用服务端组件获取数据
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getUser } from '@/entities/user';

export default async function DashboardPage() {
  const user = await getUser('current');
  return <DashboardView user={user} />;
}

// src/views/dashboard/ui/DashboardView.tsx
import type { User } from '@/entities/user';

export function DashboardView({ user }: { user: User }) {
  return <div>Welcome, {user.name}</div>;
}

故障排除

循环依赖

问题: 两个切片相互导入。

解决方案:

  1. 将共享逻辑提取到较低层(通常是实体层或共享层)
  2. 创建一个同时导入两者的较高层(组件)
  3. 检查其中一个切片是否应该拆分成多个切片
导入路径问题

问题: TypeScript 无法解析 @/ 导入。

解决方案: 在 tsconfig.json 中配置路径别名:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/app/*": ["src/app/*"],
      "@/views/*": ["src/views/*"],
      "@/widgets/*": ["src/widgets/*"],
      "@/features/*": ["src/features/*"],
      "@/entities/*": ["src/entities/*"],
      "@/shared/*": ["src/shared/*"]
    }
  }
}
构建错误

问题: 重构后 Next.js 无法找到模块。

解决方案:

  1. 清除 .next 目录:rm -rf .next
  2. 重新安装依赖项:npm install
  3. 重启开发服务器:npm run dev

配置

TypeScript 路径别名
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/app/*": ["src/app/*"],
      "@/views/*": ["src/views/*"],
      "@/widgets/*": ["src/widgets/*"],
      "@/features/*": ["src/features/*"],
      "@/entities/*": ["src/entities/*"],
      "@/shared/*": ["src/shared/*"]
    }
  },
  "include": ["src", "app"]
}
ESLint 导入规则(可选)
// .eslintrc.js
module.exports = {
  rules: {
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          {
            group: ['@/views/*', '@/widgets/*'],
            message: 'Features cannot import from views or widgets',
          },
        ],
      },
    ],
  },
};

参考资料

评论

支持 Markdown