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

16 KiB
Raw Permalink Blame History

前端开发指南

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

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 函数式组件

// 推荐:使用函数式组件 + 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