Files
2026-01-25 15:05:03 +08:00

883 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端开发指南
## 1. 开发环境搭建
### 1.1 安装依赖
```bash
# 安装Node.js
# 推荐版本:18.x 或更高
# 进入frontend目录
cd frontend
# 安装依赖
npm install
```
### 1.2 环境变量配置
```bash
# 复制环境变量模板
cp .env.example .env
# 编辑.env文件
VITE_API_BASE_URL=http://localhost:5000/api/v1
VITE_APP_TITLE=海洋项目管理系统
```
### 1.3 启动开发服务器
```bash
# 启动开发服务器
npm run dev
# 访问 http://localhost:3000
```
### 1.4 常用命令
```bash
# 开发
npm run dev
# 构建
npm run build
# 预览构建
npm run preview
# 代码检查
npm run lint
# 代码格式化
npm run format
# 运行测试
npm run test
# 测试覆盖率
npm run test:coverage
```
## 2. 代码规范
### 2.1 ESLint配置
```javascript
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:react/jsx-runtime',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', 'react-hooks'],
rules: {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
settings: {
react: {
version: 'detect',
},
},
};
```
### 2.2 Prettier配置
```javascript
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid",
"endOfLine": "lf"
}
```
### 2.3 Git提交规范
```
格式:[类型] 简短描述
类型:
- feat: 新功能
- fix: 修复bug
- style: 代码格式调整
- refactor: 重构
- test: 测试
- docs: 文档
- chore: 构建/工具链
示例:
[frontend] feat: 添加项目列表页面
[frontend] fix: 修复登录后Token未保存的问题
[frontend] style: 统一代码格式
[frontend] refactor: 重构API服务模块
[frontend] test: 添加组件单元测试
[frontend] docs: 更新开发文档
[frontend] chore: 更新依赖版本
```
## 3. 目录结构说明
### 3.1 src目录
```
src/
├── assets/ # 静态资源
│ ├── images/ # 图片
│ ├── fonts/ # 字体
│ └── styles/ # 样式文件
├── components/ # 组件
│ ├── Layout/ # 布局组件
│ ├── Common/ # 通用组件
│ └── Business/ # 业务组件
├── pages/ # 页面组件
├── contexts/ # Context上下文
├── hooks/ # 自定义Hooks
├── services/ # API服务
├── utils/ # 工具函数
├── config/ # 配置文件
└── router/ # 路由配置
```
### 3.2 文件命名规范
```
组件:PascalCase
- Button.jsx
- ProjectList.jsx
- UserForm.jsx
HookscamelCase
- useAuth.js
- useTable.js
- useApi.js
工具函数:camelCase
- formatDate.js
- formatMoney.js
- validate.js
样式文件:kebab-case
- button.css
- project-list.css
- user-form.css
```
## 4. 组件开发规范
### 4.1 函数式组件
```jsx
// 推荐:使用函数式组件 + Hooks
import React, { useState, useEffect } from 'react';
const MyComponent = ({ title, data }) => {
const [loading, setLoading] = useState(false);
useEffect(() => {
// 副作用逻辑
}, []);
return <div>{title}</div>;
};
export default MyComponent;
```
### 4.2 Props类型检查
```jsx
import PropTypes from 'prop-types';
const MyComponent = ({ title, count, onAdd }) => {
return <div>{title}: {count}</div>;
};
MyComponent.propTypes = {
title: PropTypes.string.isRequired,
count: PropTypes.number,
onAdd: PropTypes.func,
};
MyComponent.defaultProps = {
count: 0,
onAdd: () => {},
};
export default MyComponent;
```
### 4.3 事件处理
```jsx
// 推荐:使用箭头函数或bind绑定
const MyComponent = () => {
const handleClick = () => {
console.log('Clicked');
};
return <button onClick={handleClick}>Click me</button>;
};
// 不推荐:在JSX中直接定义函数
const MyComponent = () => {
return <button onClick={() => console.log('Clicked')}>Click me</button>;
};
```
## 5. Hooks使用规范
### 5.1 useState
```jsx
// 基本用法
const [count, setCount] = useState(0);
// 函数式更新
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
// 对象更新
const [user, setUser] = useState({ name: '', age: 0 });
const updateName = (name) => setUser(prev => ({ ...prev, name }));
```
### 5.2 useEffect
```jsx
// 副作用
useEffect(() => {
// 执行副作用
return () => {
// 清理函数
};
}, [dependencies]);
// 空依赖数组:只在挂载时执行一次
useEffect(() => {
console.log('Component mounted');
}, []);
// 依赖数组为空时,不要在依赖数组中省略
// 错误示例
useEffect(() => {
fetchUser(userId);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// 正确示例
useEffect(() => {
fetchUser(userId);
}, [userId]);
```
### 5.3 useMemo和useCallback
```jsx
// useMemo:缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback:缓存函数
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
```
### 5.4 自定义Hooks
```jsx
// 自定义Hook以use开头
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
setData(await response.json());
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
```
## 6. 样式开发规范
### 6.1 CSS模块
```jsx
// MyComponent.jsx
import styles from './MyComponent.module.css';
const MyComponent = () => {
return <div className={styles.container}>Content</div>;
};
```
### 6.2 CSS-in-JS(可选)
```jsx
// 使用styled-components或emotion
import styled from 'styled-components';
const StyledDiv = styled.div`
padding: 20px;
background: #f0f0f0;
&:hover {
background: #e0e0e0;
}
`;
const MyComponent = () => {
return <StyledDiv>Content</StyledDiv>;
};
```
### 6.3 Tailwind CSS(可选)
```jsx
// 使用Tailwind CSS工具类
const MyComponent = () => {
return (
<div className="p-5 bg-gray-100 hover:bg-gray-200">
Content
</div>
);
};
```
## 7. 状态管理规范
### 7.1 使用Context API
```jsx
// Context定义
import { createContext, useContext } from 'react';
const MyContext = createContext();
export const MyProvider = ({ children }) => {
const [state, setState] = useState(initialState);
return (
<MyContext.Provider value={{ state, setState }}>
{children}
</MyContext.Provider>
);
};
export const useMyContext = () => {
const context = useContext(MyContext);
if (!context) {
throw new Error('useMyContext must be used within MyProvider');
}
return context;
};
```
### 7.2 状态分类
```jsx
// 1. 本地状态:使用useState
const [value, setValue] = useState('');
// 2. 上下文状态:使用Context
const { user, setUser } = useAuth();
// 3. 表单状态:使用Ant Design Form
const [form] = Form.useForm();
// 4. URL状态:使用useSearchParams
const [searchParams, setSearchParams] = useSearchParams();
```
## 8. 路由开发规范
### 8.1 使用React Router
```jsx
import { Routes, Route, Navigate } from 'react-router-dom';
const Router = () => {
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/projects" element={<ProjectList />} />
<Route path="/projects/:id" element={<ProjectDetail />} />
</Routes>
);
};
```
### 8.2 路由参数
```jsx
import { useParams, useSearchParams } from 'react-router-dom';
const ProjectDetail = () => {
const { id } = useParams();
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword') || '';
return <div>Project ID: {id}</div>;
};
```
### 8.3 路由守卫
```jsx
const PrivateRoute = ({ children }) => {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
};
```
## 9. API调用规范
### 9.1 使用Axios
```jsx
import { useEffect } from 'react';
import { projectAPI } from '../services/project';
const ProjectList = () => {
const [list, setList] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchList = async () => {
setLoading(true);
try {
const data = await projectAPI.getList();
setList(data.items);
} catch (error) {
console.error('Fetch error:', error);
} finally {
setLoading(false);
}
};
fetchList();
}, []);
return <div>{/* 渲染列表 */}</div>;
};
```
### 9.2 使用自定义Hooks
```jsx
import { useList } from '../hooks/useList';
import { projectAPI } from '../services/project';
const ProjectList = () => {
const { list, loading, pagination, handleTableChange } = useList(
projectAPI.getList
);
return <Table {...} />;
};
```
## 10. 错误处理规范
### 10.1 错误边界
```jsx
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<App />
</ErrorBoundary>
```
### 10.2 错误处理组件
```jsx
const ErrorFallback = ({ error, resetErrorBoundary }) => {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
};
```
## 11. 性能优化
### 11.1 代码分割
```jsx
import { lazy, Suspense } from 'react';
const ProjectList = lazy(() => import('./pages/Projects/ProjectList'));
const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail'));
const App = () => {
return (
<Suspense fallback={<Spin />}>
<Routes>
<Route path="/projects" element={<ProjectList />} />
<Route path="/projects/:id" element={<ProjectDetail />} />
</Routes>
</Suspense>
);
};
```
### 11.2 React.memo
```jsx
const ExpensiveComponent = React.memo(({ data }) => {
// 渲染逻辑
});
// 自定义比较函数
const MyComponent = React.memo(({ a, b }) => {
// 渲染逻辑
}, (prevProps, nextProps) => {
return prevProps.a === nextProps.a && prevProps.b === nextProps.b;
});
```
### 11.3 useMemo和useCallback
```jsx
const MyComponent = ({ data, onUpdate }) => {
// 缓存计算结果
const sortedData = useMemo(() => {
return data.sort((a, b) => a.id - b.id);
}, [data]);
// 缓存函数
const handleClick = useCallback(() => {
onUpdate(sortedData);
}, [onUpdate, sortedData]);
return <div onClick={handleClick}>Content</div>;
};
```
## 12. 测试规范
### 12.1 组件测试
```jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
describe('Button Component', () => {
test('renders button with children', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
```
### 12.2 Hooks测试
```jsx
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
```
### 12.3 集成测试
```jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const renderWithRouter = (component) => {
return render(<BrowserRouter>{component}</BrowserRouter>);
};
test('user can login', async () => {
renderWithRouter(<App />);
fireEvent.change(screen.getByLabelText('Username'), {
target: { value: 'admin' },
});
fireEvent.change(screen.getByLabelText('Password'), {
target: { value: 'password' },
});
fireEvent.click(screen.getByText('Login'));
await waitFor(() => {
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
});
```
## 13. 调试技巧
### 13.1 React DevTools
```jsx
// 安装React DevTools浏览器扩展
// 可以查看组件树、props、state、Hooks等
```
### 13.2 Console日志
```jsx
// 开发环境下的调试日志
if (import.meta.env.DEV) {
console.log('Debug:', { data, loading });
}
// 生产环境下自动移除
```
### 13.3 性能分析
```jsx
// 使用React Profiler分析性能
import { Profiler } from 'react';
const onRenderCallback = (id, phase, actualDuration) => {
console.log(`${id} ${phase} took ${actualDuration}ms`);
};
<Profiler id="MyComponent" onRender={onRenderCallback}>
<MyComponent />
</Profiler>
```
## 14. 部署规范
### 14.1 构建配置
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
base: '/app/', // 基础路径
build: {
outDir: 'dist',
sourcemap: false, // 生产环境关闭sourcemap
chunkSizeWarningLimit: 1000,
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
},
},
},
});
```
### 14.2 环境变量
```bash
# .env.development
VITE_API_BASE_URL=http://localhost:5000/api/v1
# .env.production
VITE_API_BASE_URL=https://api.example.com/api/v1
```
### 14.3 Docker部署
```dockerfile
# Dockerfile
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
## 15. 最佳实践
### 15.1 组件设计
- 单一职责原则:每个组件只做一件事
- 组件大小控制在300行以内
- 合理拆分组件,提高复用性
- 使用组合而非继承
### 15.2 性能优化
- 使用React.memo避免不必要的渲染
- 使用useMemo缓存计算结果
- 使用useCallback缓存函数
- 使用懒加载减少初始加载时间
### 15.3 代码质量
- 遵循ESLint和Prettier规范
- 编写单元测试
- 添加必要的注释
- 保持代码简洁清晰
### 15.4 安全性
- 避免直接渲染用户输入
- 使用HTTPS
- Token存储安全
- 设置适当的CORS策略
## 16. 常见问题
### 16.1 如何处理跨域?
```javascript
// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
},
},
},
});
```
### 16.2 如何优化首屏加载?
```jsx
// 使用路由懒加载
const Dashboard = lazy(() => import('./pages/Dashboard'));
// 使用Suspense包裹
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
```
### 16.3 如何处理表单验证?
```jsx
import { Form, Input, Button } from 'antd';
const MyForm = () => {
const [form] = Form.useForm();
const onFinish = (values) => {
console.log('Form values:', values);
};
return (
<Form form={form} onFinish={onFinish} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">提交</Button>
</Form.Item>
</Form>
);
};
```
---
**文档维护**: 前端程序员
**文档类型**: 前端开发指南
**最后更新**: 2026-01-25