引言:为什么需要一份实战指南?

在当今快速发展的Web开发领域,前端技术栈日新月异。从传统的jQuery到现代的React、Vue、Angular,再到新兴的Svelte、SolidJS,开发者面临着前所未有的选择。然而,技术选型只是第一步,如何从零开始构建一个高效、可维护的现代Web应用,才是真正的挑战。

本指南将带你从项目初始化开始,逐步构建一个完整的前端项目,涵盖技术选型、架构设计、开发流程、性能优化、测试部署等关键环节。我们将以React + TypeScript + Vite为核心技术栈,结合现代前端工程化实践,为你提供一套完整的解决方案。

第一章:项目初始化与技术选型

1.1 技术栈选择

在开始项目之前,我们需要明确技术选型。对于现代Web应用,我们推荐以下技术栈:

  • 框架: React 18 + TypeScript
  • 构建工具: Vite (替代Webpack)
  • 状态管理: Zustand (轻量级替代Redux)
  • 路由: React Router v6
  • UI组件库: Ant Design 或 Material-UI (根据项目需求选择)
  • 样式方案: CSS Modules + Tailwind CSS
  • 测试: Jest + React Testing Library
  • 代码规范: ESLint + Prettier + Husky
  • 构建部署: Vercel/Netlify (CI/CD)

1.2 项目初始化

使用Vite创建React + TypeScript项目:

# 使用npm
npm create vite@latest my-app -- --template react-ts

# 或使用yarn
yarn create vite my-app --template react-ts

# 进入项目目录
cd my-app

# 安装依赖
npm install

1.3 项目结构设计

一个良好的项目结构是可维护性的基础。推荐以下结构:

src/
├── assets/          # 静态资源
├── components/      # 通用组件
│   ├── common/      # 基础组件
│   └── business/    # 业务组件
├── hooks/           # 自定义Hook
├── pages/           # 页面组件
├── services/        # API服务
├── store/           # 状态管理
├── types/           # TypeScript类型定义
├── utils/           # 工具函数
├── App.tsx          # 应用入口
└── main.tsx         # 主入口文件

1.4 配置开发环境

1.4.1 配置ESLint和Prettier

安装依赖:

npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/parser @typescript-eslint/eslint-plugin

创建.eslintrc.js

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint', 'react-hooks'],
  rules: {
    'react/react-in-jsx-scope': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    'react/prop-types': 'off',
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
};

创建.prettierrc

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 100,
  "tabWidth": 2,
  "endOfLine": "lf"
}

1.4.2 配置Husky和lint-staged

安装依赖:

npm install --save-dev husky lint-staged

初始化Husky:

npx husky install

添加pre-commit钩子:

npx husky add .husky/pre-commit "npx lint-staged"

package.json中添加:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
  }
}

第二章:核心架构设计

2.1 状态管理方案

对于中小型项目,Zustand是比Redux更轻量的选择。安装Zustand:

npm install zustand

创建一个简单的计数器Store:

// src/store/counterStore.ts
import { create } from 'zustand';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

export const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

在组件中使用:

// src/pages/CounterPage.tsx
import React from 'react';
import { useCounterStore } from '../store/counterStore';

const CounterPage: React.FC = () => {
  const { count, increment, decrement, reset } = useCounterStore();

  return (
    <div className="counter-container">
      <h2>Counter: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default CounterPage;

2.2 路由管理

使用React Router v6进行路由管理:

npm install react-router-dom

配置路由:

// src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import CounterPage from './pages/CounterPage';
import Layout from './components/Layout';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<HomePage />} />
          <Route path="counter" element={<CounterPage />} />
        </Route>
      </Routes>
    </Router>
  );
}

export default App;

2.3 API服务封装

创建统一的API服务层,便于管理和维护:

// src/services/api.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

// 创建axios实例
const api: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器
api.interceptors.request.use(
  (config) => {
    // 添加认证token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
api.interceptors.response.use(
  (response) => {
    // 处理响应数据
    return response;
  },
  (error) => {
    // 处理错误
    if (error.response) {
      const { status, data } = error.response;
      switch (status) {
        case 401:
          // 未授权,跳转登录页
          window.location.href = '/login';
          break;
        case 403:
          // 无权限
          console.error('无权限访问');
          break;
        case 404:
          // 资源不存在
          console.error('资源不存在');
          break;
        case 500:
          // 服务器错误
          console.error('服务器错误');
          break;
        default:
          console.error('请求失败');
      }
    }
    return Promise.reject(error);
  }
);

// 封装通用请求方法
export const request = async <T>(
  config: AxiosRequestConfig
): Promise<AxiosResponse<T>> => {
  try {
    const response = await api.request<T>(config);
    return response;
  } catch (error) {
    throw error;
  }
};

// 具体API接口
export const userAPI = {
  login: (data: { username: string; password: string }) =>
    request<{ token: string }>({
      method: 'POST',
      url: '/auth/login',
      data,
    }),
  
  getUserInfo: () =>
    request<{ id: string; name: string; email: string }>({
      method: 'GET',
      url: '/user/info',
    }),
  
  updateUser: (data: Partial<{ name: string; email: string }>) =>
    request({
      method: 'PUT',
      url: '/user/update',
      data,
    }),
};

export const productAPI = {
  getProducts: (params?: { page?: number; size?: number; category?: string }) =>
    request<{ data: any[]; total: number }>({
      method: 'GET',
      url: '/products',
      params,
    }),
  
  getProductDetail: (id: string) =>
    request<{ id: string; name: string; price: number }>({
      method: 'GET',
      url: `/products/${id}`,
    }),
};

2.4 自定义Hook封装

创建可复用的自定义Hook:

// src/hooks/useFetch.ts
import { useState, useEffect } from 'react';

interface FetchState<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
}

export function useFetch<T>(url: string, options?: RequestInit): FetchState<T> {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      
      try {
        const response = await fetch(url, options);
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url, JSON.stringify(options)]);

  return { data, loading, error };
}

// 使用示例
// const { data, loading, error } = useFetch<User[]>('/api/users');

第三章:组件开发最佳实践

3.1 组件设计原则

3.1.1 单一职责原则

每个组件应该只做一件事。例如,一个按钮组件应该只负责渲染按钮,而不应该包含业务逻辑。

// src/components/common/Button.tsx
import React from 'react';
import './Button.css';

interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  type?: 'button' | 'submit' | 'reset';
}

const Button: React.FC<ButtonProps> = ({
  children,
  onClick,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  type = 'button',
}) => {
  const baseClasses = 'btn';
  const variantClasses = {
    primary: 'btn-primary',
    secondary: 'btn-secondary',
    danger: 'btn-danger',
  };
  const sizeClasses = {
    small: 'btn-sm',
    medium: 'btn-md',
    large: 'btn-lg',
  };

  return (
    <button
      className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
      onClick={onClick}
      disabled={disabled}
      type={type}
    >
      {children}
    </button>
  );
};

export default Button;

3.1.2 受控组件与非受控组件

在表单处理中,正确使用受控和非受控组件:

// 受控组件示例
import React, { useState } from 'react';

const ControlledForm: React.FC = () => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('提交数据:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="姓名"
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="邮箱"
      />
      <button type="submit">提交</button>
    </form>
  );
};

// 非受控组件示例
import React, { useRef } from 'react';

const UncontrolledForm: React.FC = () => {
  const nameRef = useRef<HTMLInputElement>(null);
  const emailRef = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const name = nameRef.current?.value || '';
    const email = emailRef.current?.value || '';
    console.log('提交数据:', { name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} type="text" placeholder="姓名" />
      <input ref={emailRef} type="email" placeholder="邮箱" />
      <button type="submit">提交</button>
    </form>
  );
};

3.2 组件通信

3.2.1 Props传递

// 父组件
import React from 'react';
import UserProfile from './UserProfile';

const ParentComponent: React.FC = () => {
  const user = {
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
  };

  return (
    <div>
      <UserProfile user={user} />
    </div>
  );
};

// 子组件
import React from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserProfileProps {
  user: User;
}

const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
};

3.2.2 Context API

对于跨层级的组件通信,使用Context API:

// src/contexts/ThemeContext.tsx
import React, { createContext, useContext, useState, ReactNode } from 'react';

type Theme = 'light' | 'dark';

interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const [theme, setTheme] = useState<Theme>('light');

  const toggleTheme = () => {
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

// 使用示例
// 在App.tsx中包裹组件
// <ThemeProvider>
//   <App />
// </ThemeProvider>

// 在任意组件中使用
// const { theme, toggleTheme } = useTheme();

3.3 组件性能优化

3.3.1 React.memo和useMemo

import React, { memo, useMemo, useState } from 'react';

// 使用React.memo避免不必要的重新渲染
const ExpensiveComponent: React.FC<{ data: string[] }> = memo(({ data }) => {
  console.log('ExpensiveComponent渲染');
  return (
    <div>
      {data.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
});

// 使用useMemo缓存计算结果
const ParentComponent: React.FC = () => {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState(['item1', 'item2', 'item3']);

  // 缓存计算结果,只有items变化时才重新计算
  const expensiveCalculation = useMemo(() => {
    console.log('执行昂贵计算');
    return items.map(item => item.toUpperCase());
  }, [items]);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        重新渲染父组件: {count}
      </button>
      <ExpensiveComponent data={expensiveCalculation} />
    </div>
  );
};

3.3.2 useCallback

import React, { useCallback, useState } from 'react';

const ChildComponent: React.FC<{ onClick: () => void }> = ({ onClick }) => {
  console.log('ChildComponent渲染');
  return <button onClick={onClick}>点击我</button>;
};

const ParentComponent: React.FC = () => {
  const [count, setCount] = useState(0);

  // 使用useCallback缓存函数引用
  const handleClick = useCallback(() => {
    console.log('按钮被点击');
    setCount(prev => prev + 1);
  }, []); // 空依赖数组,函数不会重新创建

  return (
    <div>
      <p>计数: {count}</p>
      <ChildComponent onClick={handleClick} />
    </div>
  );
};

第四章:状态管理进阶

4.1 复杂状态管理

当应用状态变得复杂时,需要更结构化的状态管理:

// src/store/userStore.ts
import { create } from 'zustand';
import { userAPI } from '../services/api';

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
}

interface UserState {
  user: User | null;
  loading: boolean;
  error: string | null;
  fetchUser: () => Promise<void>;
  updateUser: (data: Partial<User>) => Promise<void>;
  logout: () => void;
}

export const useUserStore = create<UserState>((set, get) => ({
  user: null,
  loading: false,
  error: null,
  
  fetchUser: async () => {
    set({ loading: true, error: null });
    try {
      const response = await userAPI.getUserInfo();
      set({ user: response.data, loading: false });
    } catch (error) {
      set({ error: '获取用户信息失败', loading: false });
    }
  },
  
  updateUser: async (data) => {
    set({ loading: true, error: null });
    try {
      await userAPI.updateUser(data);
      const response = await userAPI.getUserInfo();
      set({ user: response.data, loading: false });
    } catch (error) {
      set({ error: '更新用户信息失败', loading: false });
    }
  },
  
  logout: () => {
    localStorage.removeItem('token');
    set({ user: null });
    window.location.href = '/login';
  },
}));

4.2 状态持久化

使用zustand/middleware实现状态持久化:

npm install zustand/middleware
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

export const useCounterStore = create(
  persist<CounterState>(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
    }),
    {
      name: 'counter-storage', // 存储在localStorage中的key
      storage: createJSONStorage(() => localStorage), // 使用localStorage
      partialize: (state) => ({ count: state.count }), // 只持久化count
    }
  )
);

第五章:样式与主题管理

5.1 CSS Modules

CSS Modules提供局部作用域,避免样式冲突:

// src/components/Button/Button.module.css
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
}

.primary {
  background-color: #1890ff;
  color: white;
}

.primary:hover {
  background-color: #40a9ff;
}

.secondary {
  background-color: #f0f0f0;
  color: #333;
}

.secondary:hover {
  background-color: #e0e0e0;
}

.disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
// src/components/Button/Button.tsx
import React from 'react';
import styles from './Button.module.css';

interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({
  children,
  onClick,
  variant = 'primary',
  disabled = false,
}) => {
  const buttonClasses = [
    styles.button,
    styles[variant],
    disabled ? styles.disabled : '',
  ].join(' ');

  return (
    <button className={buttonClasses} onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
};

export default Button;

5.2 主题管理

使用CSS变量实现主题切换:

// src/styles/theme.css
:root {
  --primary-color: #1890ff;
  --secondary-color: #f0f0f0;
  --text-color: #333;
  --background-color: #fff;
  --border-color: #d9d9d9;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
  --primary-color: #177ddc;
  --secondary-color: #1f1f1f;
  --text-color: #f0f0f0;
  --background-color: #141414;
  --border-color: #434343;
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
// src/components/ThemeToggle/ThemeToggle.tsx
import React, { useEffect } from 'react';
import { useTheme } from '../contexts/ThemeContext';

const ThemeToggle: React.FC = () => {
  const { theme, toggleTheme } = useTheme();

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
  }, [theme]);

  return (
    <button onClick={toggleTheme}>
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
};

export default ThemeToggle;

第六章:测试策略

6.1 单元测试

使用Jest和React Testing Library进行单元测试:

npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event ts-jest @types/jest

配置Jest:

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
};

创建测试文件:

// src/components/Button/Button.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';

describe('Button Component', () => {
  test('renders children correctly', () => {
    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 me</Button>);
    
    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  test('is disabled when disabled prop is true', () => {
    render(<Button disabled>Click me</Button>);
    const button = screen.getByText('Click me');
    expect(button).toBeDisabled();
  });
});

6.2 集成测试

// src/pages/CounterPage.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import CounterPage from './CounterPage';
import { useCounterStore } from '../store/counterStore';

// Mock the store
jest.mock('../store/counterStore', () => ({
  useCounterStore: jest.fn(),
}));

describe('CounterPage', () => {
  const mockIncrement = jest.fn();
  const mockDecrement = jest.fn();
  const mockReset = jest.fn();

  beforeEach(() => {
    (useCounterStore as jest.Mock).mockReturnValue({
      count: 0,
      increment: mockIncrement,
      decrement: mockDecrement,
      reset: mockReset,
    });
  });

  test('renders counter value', () => {
    render(<CounterPage />);
    expect(screen.getByText('Counter: 0')).toBeInTheDocument();
  });

  test('calls increment when + button is clicked', () => {
    render(<CounterPage />);
    fireEvent.click(screen.getByText('+'));
    expect(mockIncrement).toHaveBeenCalled();
  });

  test('calls decrement when - button is clicked', () => {
    render(<CounterPage />);
    fireEvent.click(screen.getByText('-'));
    expect(mockDecrement).toHaveBeenCalled();
  });

  test('calls reset when Reset button is clicked', () => {
    render(<CounterPage />);
    fireEvent.click(screen.getByText('Reset'));
    expect(mockReset).toHaveBeenCalled();
  });
});

第七章:性能优化

7.1 代码分割

使用React.lazy和Suspense实现代码分割:

// src/App.tsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

// 懒加载页面组件
const HomePage = lazy(() => import('./pages/HomePage'));
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));

// 加载中组件
const Loading: React.FC = () => (
  <div className="loading">加载中...</div>
);

function App() {
  return (
    <Router>
      <Suspense fallback={<Loading />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/dashboard" element={<DashboardPage />} />
          <Route path="/settings" element={<SettingsPage />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

export default App;

7.2 图片优化

// src/components/Image/Image.tsx
import React, { useState, useEffect } from 'react';

interface ImageProps {
  src: string;
  alt: string;
  width?: number;
  height?: number;
  lazy?: boolean;
  placeholder?: string;
}

const Image: React.FC<ImageProps> = ({
  src,
  alt,
  width,
  height,
  lazy = true,
  placeholder = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiNmMGYwZjAiLz48L3N2Zz4=',
}) => {
  const [imageSrc, setImageSrc] = useState<string>(placeholder);
  const [isLoaded, setIsLoaded] = useState<boolean>(false);

  useEffect(() => {
    if (!lazy) {
      setImageSrc(src);
      return;
    }

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const img = new Image();
            img.src = src;
            img.onload = () => {
              setImageSrc(src);
              setIsLoaded(true);
            };
            observer.disconnect();
          }
        });
      },
      { rootMargin: '50px' }
    );

    const imgElement = document.createElement('img');
    imgElement.style.display = 'none';
    document.body.appendChild(imgElement);
    observer.observe(imgElement);

    return () => {
      observer.disconnect();
      document.body.removeChild(imgElement);
    };
  }, [src, lazy]);

  return (
    <img
      src={imageSrc}
      alt={alt}
      width={width}
      height={height}
      style={{ opacity: isLoaded ? 1 : 0.5, transition: 'opacity 0.3s' }}
    />
  );
};

export default Image;

7.3 虚拟列表

对于长列表渲染,使用虚拟滚动:

npm install react-window
// src/components/VirtualList/VirtualList.tsx
import React from 'react';
import { FixedSizeList as List } from 'react-window';

interface VirtualListProps {
  items: any[];
  itemHeight: number;
  width?: number;
  height?: number;
}

const VirtualList: React.FC<VirtualListProps> = ({
  items,
  itemHeight,
  width = '100%',
  height = 400,
}) => {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <div style={{ padding: '10px', borderBottom: '1px solid #eee' }}>
        {items[index].name}
      </div>
    </div>
  );

  return (
    <List
      height={height}
      itemCount={items.length}
      itemSize={itemHeight}
      width={width}
    >
      {Row}
    </List>
  );
};

export default VirtualList;

第八章:构建与部署

8.1 构建配置

Vite的构建配置:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
  build: {
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom', 'react-router-dom'],
          ui: ['antd', '@ant-design/icons'],
          charts: ['echarts', 'recharts'],
        },
      },
    },
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

8.2 环境变量管理

创建环境变量文件:

# .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_NAME=MyApp-Dev

# .env.production
VITE_API_BASE_URL=https://api.myapp.com/api
VITE_APP_NAME=MyApp

在代码中使用:

// src/services/api.ts
const api: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
});

8.3 CI/CD配置

8.3.1 GitHub Actions配置

# .github/workflows/deploy.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
      
      - name: Build
        run: npm run build
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

8.3.2 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/nginx.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
server {
    listen 80;
    server_name localhost;
    
    root /usr/share/nginx/html;
    index index.html;
    
    # 启用Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    
    # 单页应用路由支持
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # API代理
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

第九章:错误处理与监控

9.1 全局错误处理

// src/utils/errorHandler.ts
import React from 'react';

// 全局错误边界组件
class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean; error: Error | null }
> {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // 发送到错误监控服务
    this.reportError(error, errorInfo);
  }

  reportError(error: Error, errorInfo: React.ErrorInfo) {
    // 这里可以集成Sentry、LogRocket等错误监控服务
    const errorData = {
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
    };
    
    // 发送到后端
    fetch('/api/error-report', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorData),
    }).catch(() => {
      // 如果发送失败,可以存储到本地
      const errors = JSON.parse(localStorage.getItem('errorLogs') || '[]');
      errors.push(errorData);
      localStorage.setItem('errorLogs', JSON.stringify(errors.slice(-50)));
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>抱歉,发生了错误</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            重试
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

9.2 API错误处理

// src/services/api.ts (扩展)
import axios, { AxiosError } from 'axios';

// 错误类型定义
interface ApiError {
  code: number;
  message: string;
  details?: any;
}

// 错误处理函数
export const handleApiError = (error: AxiosError<ApiError>) => {
  if (error.response) {
    const { status, data } = error.response;
    
    switch (status) {
      case 400:
        return { message: data.message || '请求参数错误' };
      case 401:
        // 未授权,清除token并跳转登录页
        localStorage.removeItem('token');
        window.location.href = '/login';
        return { message: '请重新登录' };
      case 403:
        return { message: '无权限访问' };
      case 404:
        return { message: '资源不存在' };
      case 429:
        return { message: '请求过于频繁,请稍后重试' };
      case 500:
        return { message: '服务器内部错误' };
      default:
        return { message: '请求失败' };
    }
  } else if (error.request) {
    return { message: '网络连接失败,请检查网络' };
  } else {
    return { message: '请求配置错误' };
  }
};

// 在API请求中使用
export const request = async <T>(
  config: AxiosRequestConfig
): Promise<AxiosResponse<T>> => {
  try {
    const response = await api.request<T>(config);
    return response;
  } catch (error) {
    const apiError = handleApiError(error as AxiosError<ApiError>);
    // 可以在这里显示错误提示
    showNotification(apiError.message, 'error');
    throw error;
  }
};

第十章:性能监控与优化

10.1 性能指标监控

// src/utils/performanceMonitor.ts
interface PerformanceMetrics {
  fcp: number; // First Contentful Paint
  lcp: number; // Largest Contentful Paint
  cls: number; // Cumulative Layout Shift
  fid: number; // First Input Delay
  ttfb: number; // Time to First Byte
  loadTime: number;
}

class PerformanceMonitor {
  private metrics: Partial<PerformanceMetrics> = {};
  private observer: PerformanceObserver | null = null;

  constructor() {
    this.initPerformanceObserver();
    this.initNavigationTiming();
  }

  private initPerformanceObserver() {
    if ('PerformanceObserver' in window) {
      // 监控LCP
      const lcpObserver = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];
        this.metrics.lcp = lastEntry.startTime;
      });
      lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });

      // 监控CLS
      const clsObserver = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        entries.forEach((entry) => {
          if (!entry.hadRecentInput) {
            this.metrics.cls = (this.metrics.cls || 0) + entry.value;
          }
        });
      });
      clsObserver.observe({ entryTypes: ['layout-shift'] });

      // 监控FID
      const fidObserver = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        entries.forEach((entry) => {
          this.metrics.fid = entry.processingStart - entry.startTime;
        });
      });
      fidObserver.observe({ entryTypes: ['first-input'] });
    }
  }

  private initNavigationTiming() {
    if ('performance' in window) {
      const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
      
      if (navigation) {
        this.metrics.ttfb = navigation.responseStart - navigation.requestStart;
        this.metrics.loadTime = navigation.loadEventEnd - navigation.startTime;
        
        // 监控FCP
        const paintEntries = performance.getEntriesByType('paint');
        const fcpEntry = paintEntries.find(entry => entry.name === 'first-contentful-paint');
        if (fcpEntry) {
          this.metrics.fcp = fcpEntry.startTime;
        }
      }
    }
  }

  public getMetrics(): PerformanceMetrics {
    return {
      fcp: this.metrics.fcp || 0,
      lcp: this.metrics.lcp || 0,
      cls: this.metrics.cls || 0,
      fid: this.metrics.fid || 0,
      ttfb: this.metrics.ttfb || 0,
      loadTime: this.metrics.loadTime || 0,
    };
  }

  public reportMetrics() {
    const metrics = this.getMetrics();
    
    // 发送到监控服务
    fetch('/api/performance', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ...metrics,
        url: window.location.href,
        timestamp: new Date().toISOString(),
        userAgent: navigator.userAgent,
      }),
    }).catch(() => {
      // 失败时存储到本地
      const reports = JSON.parse(localStorage.getItem('perfReports') || '[]');
      reports.push({ ...metrics, timestamp: new Date().toISOString() });
      localStorage.setItem('perfReports', JSON.stringify(reports.slice(-10)));
    });
  }
}

// 使用示例
const monitor = new PerformanceMonitor();

// 页面加载完成后报告性能指标
window.addEventListener('load', () => {
  setTimeout(() => {
    monitor.reportMetrics();
  }, 1000);
});

10.2 优化建议

根据性能指标,我们可以采取以下优化措施:

  1. LCP优化:

    • 优化图片和视频资源
    • 使用CDN加速静态资源
    • 实现懒加载
    • 减少阻塞渲染的JavaScript
  2. CLS优化:

    • 为图片和视频设置明确的尺寸
    • 避免在现有内容上方插入内容
    • 使用transform动画而不是top/left动画
  3. FID优化:

    • 减少JavaScript执行时间
    • 使用Web Workers处理复杂计算
    • 代码分割和懒加载

第十一章:安全最佳实践

11.1 XSS防护

// src/utils/security.ts
import DOMPurify from 'dompurify';

// HTML净化函数
export const sanitizeHTML = (html: string): string => {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'span'],
    ALLOWED_ATTR: ['href', 'title', 'class'],
    ALLOW_DATA_ATTR: false,
    FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed'],
    FORBID_ATTR: ['onerror', 'onclick', 'onload', 'onmouseover'],
  });
};

// 安全的URL验证
export const isValidUrl = (url: string): boolean => {
  try {
    const parsed = new URL(url);
    const allowedProtocols = ['http:', 'https:'];
    return allowedProtocols.includes(parsed.protocol);
  } catch {
    return false;
  }
};

// 安全的JSON解析
export const safeJSONParse = <T>(str: string): T | null => {
  try {
    return JSON.parse(str);
  } catch {
    return null;
  }
};

11.2 CSRF防护

// src/utils/csrf.ts
// 生成CSRF Token
export const generateCSRFToken = (): string => {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
};

// 存储CSRF Token
export const storeCSRFToken = (token: string): void => {
  localStorage.setItem('csrf_token', token);
};

// 获取CSRF Token
export const getCSRFToken = (): string | null => {
  return localStorage.getItem('csrf_token');
};

// 在API请求中添加CSRF Token
export const addCSRFToken = (config: RequestInit): RequestInit => {
  const token = getCSRFToken();
  if (token) {
    return {
      ...config,
      headers: {
        ...config.headers,
        'X-CSRF-Token': token,
      },
    };
  }
  return config;
};

11.3 敏感信息保护

// src/utils/sensitiveData.ts
// 敏感数据脱敏
export const maskSensitiveData = (data: string, type: 'phone' | 'id' | 'email'): string => {
  switch (type) {
    case 'phone':
      // 手机号脱敏:138****1234
      return data.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2');
    case 'id':
      // 身份证脱敏:110101****1234
      return data.replace(/^(\d{6})\d{8}(\d{4})$/, '$1****$2');
    case 'email':
      // 邮箱脱敏:a***@example.com
      const [local, domain] = data.split('@');
      if (local.length > 3) {
        return `${local[0]}***@${domain}`;
      }
      return data;
    default:
      return data;
  }
};

// 安全的本地存储
export const secureLocalStorage = {
  setItem: (key: string, value: any): void => {
    const encrypted = btoa(JSON.stringify(value));
    localStorage.setItem(key, encrypted);
  },
  getItem: <T>(key: string): T | null => {
    const encrypted = localStorage.getItem(key);
    if (!encrypted) return null;
    try {
      return JSON.parse(atob(encrypted));
    } catch {
      return null;
    }
  },
  removeItem: (key: string): void => {
    localStorage.removeItem(key);
  },
};

第十二章:移动端适配

12.1 响应式设计

/* src/styles/responsive.css */
/* 移动端优先的响应式设计 */

/* 基础样式(移动端) */
.container {
  width: 100%;
  padding: 16px;
  box-sizing: border-box;
}

/* 平板设备(≥768px) */
@media (min-width: 768px) {
  .container {
    max-width: 720px;
    margin: 0 auto;
    padding: 24px;
  }
}

/* 桌面设备(≥992px) */
@media (min-width: 992px) {
  .container {
    max-width: 960px;
    padding: 32px;
  }
}

/* 大桌面设备(≥1200px) */
@media (min-width: 1200px) {
  .container {
    max-width: 1140px;
  }
}

/* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
  /* 增大点击区域 */
  button, .btn {
    min-height: 44px;
    min-width: 44px;
  }
  
  /* 增加触摸反馈 */
  button:active {
    opacity: 0.7;
  }
}

12.2 移动端优化

// src/utils/mobileUtils.ts
// 检测移动设备
export const isMobile = (): boolean => {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
    navigator.userAgent
  );
};

// 检测iOS设备
export const isIOS = (): boolean => {
  return /iPad|iPhone|iPod/.test(navigator.userAgent);
};

// 检测Android设备
export const isAndroid = (): boolean => {
  return /Android/.test(navigator.userAgent);
};

// 检测是否支持触摸
export const isTouchDevice = (): boolean => {
  return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
};

// 移动端视口设置
export const setViewport = (): void => {
  if (isMobile()) {
    const viewport = document.querySelector('meta[name="viewport"]');
    if (viewport) {
      viewport.setAttribute(
        'content',
        'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
      );
    }
  }
};

// 防止iOS双击缩放
export const preventDoubleTapZoom = (): void => {
  if (isIOS()) {
    document.addEventListener('touchstart', (event) => {
      if (event.touches.length > 1) {
        event.preventDefault();
      }
    });
    
    let lastTouchEnd = 0;
    document.addEventListener('touchend', (event) => {
      const now = Date.now();
      if (now - lastTouchEnd <= 300) {
        event.preventDefault();
      }
      lastTouchEnd = now;
    }, false);
  }
};

第十三章:国际化与本地化

13.1 多语言支持

npm install i18next react-i18next
// src/i18n/config.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';

// 语言资源
const resources = {
  en: {
    translation: {
      welcome: 'Welcome',
      login: 'Login',
      logout: 'Logout',
      home: 'Home',
      dashboard: 'Dashboard',
      settings: 'Settings',
      error: {
        notFound: 'Page not found',
        serverError: 'Server error',
      },
    },
  },
  zh: {
    translation: {
      welcome: '欢迎',
      login: '登录',
      logout: '退出',
      home: '首页',
      dashboard: '仪表盘',
      settings: '设置',
      error: {
        notFound: '页面未找到',
        serverError: '服务器错误',
      },
    },
  },
  ja: {
    translation: {
      welcome: 'ようこそ',
      login: 'ログイン',
      logout: 'ログアウト',
      home: 'ホーム',
      dashboard: 'ダッシュボード',
      settings: '設定',
      error: {
        notFound: 'ページが見つかりません',
        serverError: 'サーバーエラー',
      },
    },
  },
};

i18n
  .use(HttpApi)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    fallbackLng: 'en',
    debug: import.meta.env.DEV,
    interpolation: {
      escapeValue: false,
    },
    detection: {
      order: ['cookie', 'localStorage', 'navigator', 'htmlTag'],
      caches: ['cookie', 'localStorage'],
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
  });

export default i18n;

13.2 在组件中使用

// src/components/Header/Header.tsx
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';

const Header: React.FC = () => {
  const { t, i18n } = useTranslation();
  const navigate = useNavigate();

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng);
    localStorage.setItem('language', lng);
  };

  return (
    <header className="header">
      <div className="header-left">
        <h1 onClick={() => navigate('/')}>{t('welcome')}</h1>
      </div>
      
      <div className="header-right">
        <button onClick={() => changeLanguage('en')}>EN</button>
        <button onClick={() => changeLanguage('zh')}>中文</button>
        <button onClick={() => changeLanguage('ja')}>日本語</button>
        
        <nav>
          <button onClick={() => navigate('/')}>{t('home')}</button>
          <button onClick={() => navigate('/dashboard')}>{t('dashboard')}</button>
          <button onClick={() => navigate('/settings')}>{t('settings')}</button>
        </nav>
      </div>
    </header>
  );
};

export default Header;

第十四章:可访问性(A11y)

14.1 语义化HTML

// src/components/AccessibleButton/AccessibleButton.tsx
import React from 'react';

interface AccessibleButtonProps {
  children: React.ReactNode;
  onClick: () => void;
  ariaLabel?: string;
  ariaDescribedBy?: string;
  ariaPressed?: boolean;
  disabled?: boolean;
}

const AccessibleButton: React.FC<AccessibleButtonProps> = ({
  children,
  onClick,
  ariaLabel,
  ariaDescribedBy,
  ariaPressed,
  disabled = false,
}) => {
  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={ariaLabel}
      aria-describedby={ariaDescribedBy}
      aria-pressed={ariaPressed}
      disabled={disabled}
      className="accessible-button"
    >
      {children}
    </button>
  );
};

export default AccessibleButton;

14.2 键盘导航支持

// src/hooks/useKeyboardNavigation.ts
import { useEffect, useRef } from 'react';

interface KeyboardNavigationOptions {
  onEnter?: () => void;
  onEscape?: () => void;
  onArrowUp?: () => void;
  onArrowDown?: () => void;
  onArrowLeft?: () => void;
  onArrowRight?: () => void;
  onTab?: () => void;
}

export const useKeyboardNavigation = (
  ref: React.RefObject<HTMLElement>,
  options: KeyboardNavigationOptions
) => {
  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const handleKeyDown = (event: KeyboardEvent) => {
      switch (event.key) {
        case 'Enter':
          options.onEnter?.();
          break;
        case 'Escape':
          options.onEscape?.();
          break;
        case 'ArrowUp':
          event.preventDefault();
          options.onArrowUp?.();
          break;
        case 'ArrowDown':
          event.preventDefault();
          options.onArrowDown?.();
          break;
        case 'ArrowLeft':
          event.preventDefault();
          options.onArrowLeft?.();
          break;
        case 'ArrowRight':
          event.preventDefault();
          options.onArrowRight?.();
          break;
        case 'Tab':
          options.onTab?.();
          break;
      }
    };

    element.addEventListener('keydown', handleKeyDown);
    return () => element.removeEventListener('keydown', handleKeyDown);
  }, [ref, options]);
};

// 使用示例
const Dropdown: React.FC = () => {
  const dropdownRef = useRef<HTMLDivElement>(null);
  const [isOpen, setIsOpen] = useState(false);

  useKeyboardNavigation(dropdownRef, {
    onEnter: () => setIsOpen(!isOpen),
    onEscape: () => setIsOpen(false),
    onArrowDown: () => {
      if (!isOpen) setIsOpen(true);
    },
  });

  return (
    <div ref={dropdownRef} tabIndex={0} className="dropdown">
      <button onClick={() => setIsOpen(!isOpen)}>选项</button>
      {isOpen && (
        <ul role="menu">
          <li role="menuitem">选项1</li>
          <li role="menuitem">选项2</li>
          <li role="menuitem">选项3</li>
        </ul>
      )}
    </div>
  );
};

第十五章:项目维护与文档

15.1 代码注释规范

/**
 * 用户管理模块
 * @description 提供用户相关的API接口和状态管理
 * @version 1.0.0
 * @author 张三
 * @created 2024-01-01
 */

// 文件头部注释
// src/services/userService.ts

// 函数注释
/**
 * 获取用户信息
 * @param userId - 用户ID
 * @returns Promise<User> - 用户信息
 * @throws Error - 当请求失败时抛出错误
 * @example
 * ```typescript
 * const user = await getUserInfo('123');
 * console.log(user.name);
 * ```
 */
export async function getUserInfo(userId: string): Promise<User> {
  // 实现代码
}

// 类注释
/**
 * 用户管理类
 * @description 管理用户相关的业务逻辑
 */
class UserManager {
  /**
   * 构造函数
   * @param api - API客户端实例
   */
  constructor(private api: ApiClient) {}

  /**
   * 创建用户
   * @param userData - 用户数据
   * @returns Promise<User> - 创建的用户
   */
  async createUser(userData: CreateUserDto): Promise<User> {
    // 实现代码
  }
}

15.2 项目文档

创建README.md

# EB前端项目

## 项目简介

这是一个基于React + TypeScript + Vite构建的现代Web应用。

## 技术栈

- React 18
- TypeScript
- Vite
- Zustand
- React Router v6
- ESLint + Prettier
- Jest + React Testing Library

## 快速开始

### 安装依赖

```bash
npm install

开发环境

npm run dev

构建

npm run build

测试

npm run test

代码规范

npm run lint

项目结构

src/
├── assets/          # 静态资源
├── components/      # 组件
├── hooks/           # 自定义Hook
├── pages/           # 页面
├── services/        # API服务
├── store/           # 状态管理
├── types/           # 类型定义
├── utils/           # 工具函数
└── App.tsx          # 应用入口

开发规范

组件开发

  1. 使用函数组件和Hooks
  2. 组件文件使用.tsx扩展名
  3. 样式使用CSS Modules
  4. 每个组件应该有对应的测试文件

提交规范

使用Conventional Commits:

feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式
refactor: 重构
test: 测试相关
chore: 构建/工具相关

部署

Vercel部署

  1. 安装Vercel CLI: npm i -g vercel
  2. 登录: vercel login
  3. 部署: vercel --prod

Docker部署

docker build -t my-app .
docker run -p 80:80 my-app

环境变量

创建.env文件:

VITE_API_BASE_URL=http://localhost:8080/api

贡献指南

  1. Fork项目
  2. 创建特性分支: git checkout -b feature/amazing-feature
  3. 提交更改: git commit -m 'feat: add amazing feature'
  4. 推送分支: git push origin feature/amazing-feature
  5. 创建Pull Request

许可证

MIT License


## 第十六章:总结与展望

### 16.1 项目回顾

通过本指南,我们从零开始构建了一个完整的现代Web应用,涵盖了:

1. **项目初始化**:技术选型、环境配置、代码规范
2. **架构设计**:状态管理、路由、API服务、组件设计
3. **开发实践**:组件开发、性能优化、测试策略
4. **构建部署**:构建配置、CI/CD、Docker化
5. **高级主题**:错误处理、性能监控、安全、移动端适配、国际化、可访问性

### 16.2 未来优化方向

1. **微前端架构**:对于大型应用,可以考虑微前端架构
2. **服务端渲染**:使用Next.js或Remix提升SEO和首屏性能
3. **PWA支持**:添加Service Worker,实现离线访问
4. **WebAssembly**:在性能敏感场景使用WebAssembly
5. **AI集成**:集成机器学习模型,提供智能功能

### 16.3 持续学习

前端技术日新月异,建议持续关注:

- React官方文档和新特性
- TypeScript类型系统进阶
- Web性能优化最佳实践
- 新兴框架和工具(如Svelte、SolidJS、Bun)
- Web标准和API更新

## 附录:常用命令速查

```bash
# 项目初始化
npm create vite@latest my-app -- --template react-ts

# 开发
npm run dev

# 构建
npm run build

# 预览构建结果
npm run preview

# 测试
npm run test

# 代码检查
npm run lint

# 格式化代码
npm run format

# 生成类型定义
npm run types

# 清理缓存
npm run clean

# 更新依赖
npm update

# 安全审计
npm audit

# 依赖分析
npx depcheck

结语

构建一个高效、可维护的现代Web应用是一个系统工程,需要良好的架构设计、规范的开发流程和持续的优化。希望本指南能为你提供清晰的路线图和实用的工具,帮助你在前端开发的道路上走得更远。

记住,优秀的代码不仅仅是能运行的代码,更是易于理解、易于维护、易于扩展的代码。保持学习,持续改进,享受编码的乐趣!


最后更新: 2024年1月
版本: 1.0.0
作者: EB前端团队