16 KiB
16 KiB
前端开发指南
1. 开发环境搭建
1.1 安装依赖
# 安装Node.js
# 推荐版本:18.x 或更高
# 进入frontend目录
cd frontend
# 安装依赖
npm install
1.2 环境变量配置
# 复制环境变量模板
cp .env.example .env
# 编辑.env文件
VITE_API_BASE_URL=http://localhost:5000/api/v1
VITE_APP_TITLE=海洋项目管理系统
1.3 启动开发服务器
# 启动开发服务器
npm run dev
# 访问 http://localhost:3000
1.4 常用命令
# 开发
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配置
// .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配置
// .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
Hooks:camelCase
- 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 函数式组件
// 推荐:使用函数式组件 + 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类型检查
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 事件处理
// 推荐:使用箭头函数或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
// 基本用法
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
// 副作用
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
// useMemo:缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback:缓存函数
const handleClick = useCallback(() => {
doSomething(a, b);
}, [a, b]);
5.4 自定义Hooks
// 自定义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模块
// MyComponent.jsx
import styles from './MyComponent.module.css';
const MyComponent = () => {
return <div className={styles.container}>Content</div>;
};
6.2 CSS-in-JS(可选)
// 使用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(可选)
// 使用Tailwind CSS工具类
const MyComponent = () => {
return (
<div className="p-5 bg-gray-100 hover:bg-gray-200">
Content
</div>
);
};
7. 状态管理规范
7.1 使用Context API
// 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 状态分类
// 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
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 路由参数
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 路由守卫
const PrivateRoute = ({ children }) => {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
};
9. API调用规范
9.1 使用Axios
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
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 错误边界
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 错误处理组件
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 代码分割
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
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
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 组件测试
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测试
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 集成测试
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
// 安装React DevTools浏览器扩展
// 可以查看组件树、props、state、Hooks等
13.2 Console日志
// 开发环境下的调试日志
if (import.meta.env.DEV) {
console.log('Debug:', { data, loading });
}
// 生产环境下自动移除
13.3 性能分析
// 使用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 构建配置
// 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 环境变量
# .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
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 如何处理跨域?
// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
},
},
},
});
16.2 如何优化首屏加载?
// 使用路由懒加载
const Dashboard = lazy(() => import('./pages/Dashboard'));
// 使用Suspense包裹
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
16.3 如何处理表单验证?
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