项目结构 #
一、基本结构 #
1.1 小型项目 #
text
my-app/
├── public/
│ └── favicon.ico
├── src/
│ ├── components/
│ │ ├── Button.jsx
│ │ ├── Input.jsx
│ │ └── Modal.jsx
│ ├── hooks/
│ │ └── useLocalStorage.js
│ ├── pages/
│ │ ├── Home.jsx
│ │ └── About.jsx
│ ├── App.jsx
│ ├── main.jsx
│ └── index.css
├── index.html
├── package.json
└── vite.config.js
1.2 中型项目 #
text
my-app/
├── public/
│ ├── favicon.ico
│ └── images/
├── src/
│ ├── components/
│ │ ├── common/
│ │ │ ├── Button/
│ │ │ │ ├── Button.jsx
│ │ │ │ ├── Button.module.css
│ │ │ │ └── index.js
│ │ │ ├── Input/
│ │ │ └── Modal/
│ │ └── layout/
│ │ ├── Header/
│ │ ├── Footer/
│ │ └── Sidebar/
│ ├── features/
│ │ ├── auth/
│ │ │ ├── components/
│ │ │ ├── hooks/
│ │ │ ├── store.js
│ │ │ └── index.js
│ │ └── cart/
│ ├── hooks/
│ │ ├── useFetch.js
│ │ ├── useLocalStorage.js
│ │ └── index.js
│ ├── pages/
│ │ ├── Home/
│ │ ├── About/
│ │ └── NotFound/
│ ├── router/
│ │ ├── index.jsx
│ │ └── routes.js
│ ├── store/
│ │ ├── index.js
│ │ └── useStore.js
│ ├── utils/
│ │ ├── api.js
│ │ ├── helpers.js
│ │ └── constants.js
│ ├── App.jsx
│ ├── main.jsx
│ └── index.css
├── index.html
├── package.json
└── vite.config.js
二、组件组织 #
2.1 组件文件夹结构 #
text
components/
├── Button/
│ ├── Button.jsx 组件实现
│ ├── Button.module.css 样式文件
│ ├── Button.test.jsx 测试文件
│ └── index.js 导出入口
2.2 组件文件示例 #
jsx
// Button/Button.jsx
import styles from './Button.module.css';
export function Button({
children,
variant = 'primary',
size = 'medium',
disabled = false,
onClick
}) {
return (
<button
class={`${styles.button} ${styles[variant]} ${styles[size]}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
// Button/index.js
export { Button } from './Button';
export { Button as default } from './Button';
2.3 组件分类 #
text
components/
├── common/ 通用组件
│ ├── Button/
│ ├── Input/
│ ├── Modal/
│ └── Loading/
├── layout/ 布局组件
│ ├── Header/
│ ├── Footer/
│ ├── Sidebar/
│ └── Layout/
└── features/ 功能组件
├── UserCard/
├── ProductList/
└── ShoppingCart/
三、特性驱动结构 #
3.1 按功能组织 #
text
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.jsx
│ │ │ └── RegisterForm.jsx
│ │ ├── hooks/
│ │ │ └── useAuth.js
│ │ ├── api/
│ │ │ └── authApi.js
│ │ ├── store.js
│ │ └── index.js
│ ├── products/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── api/
│ │ ├── store.js
│ │ └── index.js
│ └── cart/
│ ├── components/
│ ├── hooks/
│ ├── api/
│ ├── store.js
│ └── index.js
└── components/
└── common/
3.2 特性模块示例 #
jsx
// features/auth/index.js
export { LoginForm } from './components/LoginForm';
export { RegisterForm } from './components/RegisterForm';
export { useAuth } from './hooks/useAuth';
export { authStore } from './store';
// features/auth/store.js
import { signal, computed } from '@preact/signals';
export const user = signal(null);
export const isAuthenticated = computed(() => !!user.value);
export const login = async (credentials) => { /* ... */ };
export const logout = () => { user.value = null; };
// features/auth/hooks/useAuth.js
import { user, isAuthenticated, login, logout } from '../store';
export function useAuth() {
return {
user: user.value,
isAuthenticated: isAuthenticated.value,
login,
logout
};
}
四、路由组织 #
4.1 路由配置 #
jsx
// router/routes.js
export const ROUTES = {
HOME: '/',
ABOUT: '/about',
LOGIN: '/login',
REGISTER: '/register',
DASHBOARD: '/dashboard',
USER_PROFILE: (id) => `/users/${id}`,
PRODUCT_DETAIL: (id) => `/products/${id}`
};
// router/index.jsx
import { lazy, Suspense } from 'preact/compat';
import { Router } from 'preact-router';
import { ROUTES } from './routes';
const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const Login = lazy(() => import('../pages/Login'));
const Dashboard = lazy(() => import('../pages/Dashboard'));
export function AppRouter() {
return (
<Suspense fallback={<Loading />}>
<Router>
<Home path={ROUTES.HOME} />
<About path={ROUTES.ABOUT} />
<Login path={ROUTES.LOGIN} />
<Dashboard path={ROUTES.DASHBOARD} />
<NotFound default />
</Router>
</Suspense>
);
}
五、状态管理组织 #
5.1 全局状态 #
text
store/
├── index.js 统一导出
├── user.js 用户状态
├── cart.js 购物车状态
├── ui.js UI 状态
└── api.js API 状态
5.2 Store 示例 #
jsx
// store/cart.js
import { signal, computed, batch } from '@preact/signals';
export const items = signal([]);
export const isOpen = signal(false);
export const count = computed(() =>
items.value.reduce((sum, item) => sum + item.quantity, 0)
);
export const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
export const addItem = (product) => {
const existing = items.value.find(item => item.id === product.id);
if (existing) {
items.value = items.value.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
} else {
items.value = [...items.value, { ...product, quantity: 1 }];
}
};
export const removeItem = (id) => {
items.value = items.value.filter(item => item.id !== id);
};
export const clearCart = () => {
batch(() => {
items.value = [];
isOpen.value = false;
});
};
// store/index.js
export * from './user';
export * from './cart';
export * from './ui';
六、API 组织 #
6.1 API 模块 #
text
api/
├── index.js 统一导出
├── client.js HTTP 客户端
├── auth.js 认证 API
├── products.js 产品 API
└── users.js 用户 API
6.2 API 示例 #
jsx
// api/client.js
const BASE_URL = '/api';
export async function request(endpoint, options = {}) {
const url = `${BASE_URL}${endpoint}`;
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
// api/auth.js
import { request } from './client';
export const authApi = {
login: (credentials) => request('/auth/login', {
method: 'POST',
body: JSON.stringify(credentials)
}),
logout: () => request('/auth/logout', {
method: 'POST'
}),
register: (data) => request('/auth/register', {
method: 'POST',
body: JSON.stringify(data)
}),
getCurrentUser: () => request('/auth/me')
};
// api/index.js
export * from './client';
export { authApi } from './auth';
export { productsApi } from './products';
export { usersApi } from './users';
七、工具函数组织 #
7.1 工具模块 #
text
utils/
├── index.js 统一导出
├── helpers.js 通用辅助函数
├── constants.js 常量定义
├── validators.js 验证函数
└── formatters.js 格式化函数
7.2 工具示例 #
jsx
// utils/helpers.js
export const debounce = (fn, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
};
export const throttle = (fn, limit) => {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
};
export const classNames = (...classes) => {
return classes.filter(Boolean).join(' ');
};
// utils/constants.js
export const API_URL = '/api';
export const ITEMS_PER_PAGE = 10;
export const MAX_FILE_SIZE = 5 * 1024 * 1024;
export const STATUS = {
IDLE: 'idle',
LOADING: 'loading',
SUCCESS: 'success',
ERROR: 'error'
};
// utils/formatters.js
export const formatPrice = (price) => {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(price);
};
export const formatDate = (date) => {
return new Intl.DateTimeFormat('zh-CN').format(new Date(date));
};
八、最佳实践 #
8.1 命名约定 #
text
组件:PascalCase
├── Button.jsx
├── UserProfile.jsx
函数:camelCase
├── formatDate.js
├── useAuth.js
常量:UPPER_SNAKE_CASE
├── API_URL
├── MAX_ITEMS
文件夹:kebab-case 或 PascalCase
├── user-profile/
├── UserProfile/
8.2 导出规范 #
jsx
// 命名导出
export function Button() {}
export const useAuth = () => {};
// 默认导出(页面组件)
export default function HomePage() {}
// 统一导出
export { Button } from './Button';
export { Input } from './Input';
九、总结 #
| 要点 | 说明 |
|---|---|
| 组件 | 按功能分类 |
| 特性 | 按领域组织 |
| 状态 | 集中管理 |
| API | 模块化 |
| 工具 | 统一导出 |
核心原则:
- 保持结构清晰
- 按功能/领域组织
- 统一命名规范
- 便于扩展维护
最后更新:2026-03-28