React 18.x 学习计划 - 第十天:React综合实践与项目构建

学习目标

  • 掌握TypeScript与React结合使用
  • 学会使用构建工具和打包优化
  • 理解部署流程和性能优化
  • 构建完整的生产级React应用
  • 掌握常见问题解决方案

学习时间安排

总时长:8-9小时

  • TypeScript与React:2.5小时
  • 构建工具和打包:1.5小时
  • 部署和优化:1.5小时
  • 完整项目实践:3-4小时

第一部分:TypeScript与React (2.5小时)

1.1 TypeScript基础配置

安装和配置TypeScript(详细注释版)
bash 复制代码
# 安装TypeScript和相关类型定义
npm install --save-dev typescript @types/react @types/react-dom @types/node

# 安装TypeScript编译器
npm install --save-dev typescript ts-loader
TypeScript配置文件(详细注释版)
json 复制代码
// tsconfig.json
{
  "compilerOptions": {
    // 目标JavaScript版本
    "target": "ES2020",
    
    // 模块系统
    "module": "ESNext",
    
    // 模块解析策略
    "moduleResolution": "node",
    
    // 库文件
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    
    // 允许使用JSX
    "jsx": "react-jsx",
    
    // 严格模式
    "strict": true,
    
    // 启用所有严格类型检查
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    
    // 其他编译选项
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    
    // 输出选项
    "outDir": "./dist",
    "rootDir": "./src",
    
    // 声明文件
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    
    // 解析选项
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@hooks/*": ["src/hooks/*"],
      "@store/*": ["src/store/*"]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "build"
  ]
}

1.2 React组件TypeScript类型

函数组件类型定义(详细注释版)
typescript 复制代码
// src/components/Button.tsx
// 导入React
import React from 'react';

// 定义Button组件的Props接口
// 接口定义了组件接收的所有属性及其类型
interface ButtonProps {
  // 按钮文本,必需属性
  children: React.ReactNode;
  
  // 点击事件处理函数,可选属性
  onClick?: () => void;
  
  // 按钮类型,可选,默认值为'button'
  type?: 'button' | 'submit' | 'reset';
  
  // 是否禁用,可选,默认值为false
  disabled?: boolean;
  
  // 自定义样式类名,可选
  className?: string;
  
  // 按钮变体样式,可选
  variant?: 'primary' | 'secondary' | 'danger';
  
  // 按钮大小,可选
  size?: 'small' | 'medium' | 'large';
}

// 定义Button组件
// 使用React.FC泛型类型,并指定Props类型
const Button: React.FC<ButtonProps> = ({
  children,
  onClick,
  type = 'button',
  disabled = false,
  className = '',
  variant = 'primary',
  size = 'medium'
}) => {
  // 生成样式类名
  const buttonClasses = `btn btn-${variant} btn-${size} ${className}`.trim();

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

// 导出Button组件
export default Button;
带泛型的组件(详细注释版)
typescript 复制代码
// src/components/List.tsx
// 导入React
import React from 'react';

// 定义列表项接口
// 使用泛型T表示列表项的类型
interface ListItem<T> {
  id: string | number;
  data: T;
}

// 定义List组件的Props接口
// 使用泛型T表示列表项数据的类型
interface ListProps<T> {
  // 列表项数组
  items: ListItem<T>[];
  
  // 渲染函数,用于渲染每个列表项
  renderItem: (item: T) => React.ReactNode;
  
  // 列表项点击事件处理函数
  onItemClick?: (item: T) => void;
  
  // 自定义样式类名
  className?: string;
  
  // 空列表时显示的内容
  emptyMessage?: string;
}

// 定义List组件
// 使用泛型T,使组件可以处理不同类型的数据
function List<T>({
  items,
  renderItem,
  onItemClick,
  className = '',
  emptyMessage = 'No items'
}: ListProps<T>) {
  // 如果列表为空,显示空消息
  if (items.length === 0) {
    return (
      <div className={`list-empty ${className}`}>
        <p>{emptyMessage}</p>
      </div>
    );
  }

  return (
    <ul className={`list ${className}`}>
      {items.map(item => (
        <li
          key={item.id}
          className="list-item"
          onClick={() => onItemClick?.(item.data)}
        >
          {renderItem(item.data)}
        </li>
      ))}
    </ul>
  );
}

// 导出List组件
export default List;

1.3 Hooks类型定义

Hooks类型示例(详细注释版)
typescript 复制代码
// src/hooks/useCounter.ts
// 导入React
import { useState, useCallback } from 'react';

// 定义useCounter Hook的返回值接口
interface UseCounterReturn {
  // 当前计数值
  count: number;
  
  // 增加计数的函数
  increment: () => void;
  
  // 减少计数的函数
  decrement: () => void;
  
  // 重置计数的函数
  reset: () => void;
  
  // 设置计数值的函数
  setCount: (value: number) => void;
}

// 定义useCounter Hook
// 接收初始值参数,返回计数器相关的状态和函数
function useCounter(initialValue: number = 0): UseCounterReturn {
  // 使用useState Hook管理计数状态
  const [count, setCount] = useState<number>(initialValue);

  // 使用useCallback Hook记忆化增加函数
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  // 使用useCallback Hook记忆化减少函数
  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  // 使用useCallback Hook记忆化重置函数
  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);

  // 返回计数器相关的状态和函数
  return {
    count,
    increment,
    decrement,
    reset,
    setCount
  };
}

// 导出useCounter Hook
export default useCounter;
自定义Hook类型(详细注释版)
typescript 复制代码
// src/hooks/useFetch.ts
// 导入React
import { useState, useEffect } from 'react';

// 定义useFetch Hook的返回值接口
// 使用泛型T表示获取的数据类型
interface UseFetchReturn<T> {
  // 获取的数据
  data: T | null;
  
  // 是否正在加载
  loading: boolean;
  
  // 错误信息
  error: string | null;
  
  // 重新获取数据的函数
  refetch: () => void;
}

// 定义useFetch Hook
// 使用泛型T表示获取的数据类型
function useFetch<T>(
  url: string,
  options?: RequestInit
): UseFetchReturn<T> {
  // 使用useState Hook管理状态
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  // 获取数据的函数
  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      
      const response = await fetch(url, options);
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result: T = await response.json();
      setData(result);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred');
    } finally {
      setLoading(false);
    }
  };

  // 使用useEffect Hook在组件挂载时获取数据
  useEffect(() => {
    fetchData();
  }, [url, JSON.stringify(options)]);

  // 返回数据、加载状态、错误信息和重新获取函数
  return {
    data,
    loading,
    error,
    refetch: fetchData
  };
}

// 导出useFetch Hook
export default useFetch;

1.4 Redux TypeScript类型

Redux TypeScript配置(详细注释版)
typescript 复制代码
// src/store/store.ts
// 导入Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
// 导入reducers
import counterReducer from './slices/counterSlice';
import userReducer from './slices/userSlice';

// 配置Redux store
export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer
  }
});

// 导出RootState类型
// 这个类型表示整个Redux store的状态结构
export type RootState = ReturnType<typeof store.getState>;

// 导出AppDispatch类型
// 这个类型表示store的dispatch函数类型
export type AppDispatch = typeof store.dispatch;

// 导出类型化的hooks
// 这些hooks提供了类型安全的store访问
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';

// 类型化的useDispatch Hook
export const useAppDispatch = () => useDispatch<AppDispatch>();

// 类型化的useSelector Hook
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Redux Slice类型定义(详细注释版)
typescript 复制代码
// src/store/slices/counterSlice.ts
// 导入Redux Toolkit
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

// 定义Counter状态接口
interface CounterState {
  // 计数值
  value: number;
  
  // 历史记录
  history: Array<{
    action: string;
    value: number;
    timestamp: string;
  }>;
  
  // 是否正在加载
  isLoading: boolean;
}

// 定义初始状态
const initialState: CounterState = {
  value: 0,
  history: [],
  isLoading: false
};

// 创建counterSlice
const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    // 增加计数
    increment: (state) => {
      state.value += 1;
      state.history.push({
        action: 'increment',
        value: state.value,
        timestamp: new Date().toISOString()
      });
    },
    
    // 减少计数
    decrement: (state) => {
      state.value -= 1;
      state.history.push({
        action: 'decrement',
        value: state.value,
        timestamp: new Date().toISOString()
      });
    },
    
    // 设置值
    // 使用PayloadAction指定action payload的类型
    setValue: (state, action: PayloadAction<number>) => {
      state.value = action.payload;
      state.history.push({
        action: 'setValue',
        value: action.payload,
        timestamp: new Date().toISOString()
      });
    },
    
    // 设置加载状态
    setLoading: (state, action: PayloadAction<boolean>) => {
      state.isLoading = action.payload;
    }
  }
});

// 导出actions
export const { increment, decrement, setValue, setLoading } = counterSlice.actions;

// 导出reducer
export default counterSlice.reducer;

第二部分:构建工具和打包 (1.5小时)

2.1 Webpack配置

Webpack基础配置(详细注释版)
javascript 复制代码
// webpack.config.js
// 导入path模块
const path = require('path');
// 导入HtmlWebpackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 导入MiniCssExtractPlugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 导出Webpack配置
module.exports = {
  // 入口文件
  entry: './src/index.js',
  
  // 输出配置
  output: {
    // 输出目录
    path: path.resolve(__dirname, 'dist'),
    // 输出文件名
    filename: 'static/js/[name].[contenthash:8].js',
    // chunk文件名
    chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
    // 资源文件名
    assetModuleFilename: 'static/media/[name].[hash:8][ext]',
    // 清理输出目录
    clean: true
  },
  
  // 模块解析配置
  resolve: {
    // 文件扩展名
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    // 路径别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@hooks': path.resolve(__dirname, 'src/hooks'),
      '@store': path.resolve(__dirname, 'src/store')
    }
  },
  
  // 模块配置
  module: {
    rules: [
      // JavaScript和TypeScript文件处理
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              '@babel/preset-react',
              '@babel/preset-typescript'
            ]
          }
        }
      },
      
      // CSS文件处理
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      },
      
      // 图片文件处理
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024 // 8KB
          }
        }
      },
      
      // 字体文件处理
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource'
      }
    ]
  },
  
  // 插件配置
  plugins: [
    // HTML模板插件
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: 'index.html',
      inject: true
    }),
    
    // CSS提取插件
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].[contenthash:8].css',
      chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
    })
  ],
  
  // 优化配置
  optimization: {
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 第三方库
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          reuseExistingChunk: true
        },
        // React相关库
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 20,
          reuseExistingChunk: true
        },
        // 公共代码
        common: {
          name: 'common',
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
    // 运行时chunk
    runtimeChunk: {
      name: 'runtime'
    }
  },
  
  // 开发服务器配置
  devServer: {
    static: {
      directory: path.join(__dirname, 'public')
    },
    compress: true,
    port: 3000,
    hot: true,
    open: true,
    historyApiFallback: true
  },
  
  // 开发工具
  devtool: 'source-map'
};

2.2 Vite配置

Vite基础配置(详细注释版)
javascript 复制代码
// vite.config.js
// 导入vite插件
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

// 导出Vite配置
export default defineConfig({
  // 插件配置
  plugins: [
    react()
  ],
  
  // 路径解析配置
  resolve: {
    // 路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils'),
      '@hooks': path.resolve(__dirname, './src/hooks'),
      '@store': path.resolve(__dirname, './src/store')
    }
  },
  
  // 开发服务器配置
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  
  // 构建配置
  build: {
    // 输出目录
    outDir: 'dist',
    // 资源内联阈值
    assetsInlineLimit: 4096,
    // 代码分割配置
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom'],
          'router-vendor': ['react-router-dom'],
          'redux-vendor': ['@reduxjs/toolkit', 'react-redux']
        }
      }
    },
    // 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  },
  
  // 环境变量配置
  envPrefix: 'REACT_APP_'
});

第三部分:部署和优化 (1.5小时)

3.1 生产环境构建

构建脚本配置(详细注释版)
json 复制代码
// package.json构建脚本
{
  "scripts": {
    "build": "react-scripts build",
    "build:analyze": "npm run build && npx source-map-explorer 'build/static/js/*.js'",
    "build:production": "NODE_ENV=production npm run build",
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  }
}
环境变量配置(详细注释版)
javascript 复制代码
// .env.development
// 开发环境变量
REACT_APP_API_URL=http://localhost:8080/api
REACT_APP_ENV=development
REACT_APP_DEBUG=true

// .env.production
// 生产环境变量
REACT_APP_API_URL=https://api.example.com
REACT_APP_ENV=production
REACT_APP_DEBUG=false

// src/config/env.js
// 环境配置模块
const env = {
  // API基础URL
  API_URL: process.env.REACT_APP_API_URL || 'http://localhost:8080/api',
  
  // 当前环境
  ENV: process.env.REACT_APP_ENV || 'development',
  
  // 是否启用调试
  DEBUG: process.env.REACT_APP_DEBUG === 'true',
  
  // 其他配置
  VERSION: process.env.REACT_APP_VERSION || '1.0.0'
};

// 导出环境配置
export default env;

3.2 性能优化配置

代码分割优化(详细注释版)
javascript 复制代码
// src/utils/lazyLoad.js
// 导入React和lazy
import { lazy } from 'react';

// 定义懒加载组件函数
// 这个函数用于创建懒加载组件,并添加加载超时和错误处理
export function lazyLoad(importFunc, timeout = 10000) {
  return lazy(() => {
    return Promise.race([
      importFunc(),
      new Promise((_, reject) => {
        setTimeout(() => {
          reject(new Error('Component load timeout'));
        }, timeout);
      })
    ]);
  });
}

// 使用示例
// const LazyComponent = lazyLoad(() => import('./LazyComponent'));
资源优化(详细注释版)
javascript 复制代码
// src/utils/imageOptimization.js
// 图片优化工具函数

// 生成响应式图片URL
export function getResponsiveImageUrl(baseUrl, width) {
  // 根据设备宽度返回不同尺寸的图片
  if (width <= 640) {
    return `${baseUrl}?w=640`;
  } else if (width <= 1024) {
    return `${baseUrl}?w=1024`;
  } else {
    return `${baseUrl}?w=1920`;
  }
}

// 预加载图片
export function preloadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = resolve;
    img.onerror = reject;
    img.src = url;
  });
}

// 批量预加载图片
export async function preloadImages(urls) {
  const promises = urls.map(url => preloadImage(url));
  return Promise.all(promises);
}

第四部分:完整项目实践 (3-4小时)

项目:企业级任务管理系统

项目结构(详细注释版)
复制代码
task-management-system/
├── public/
│   ├── index.html
│   ├── favicon.ico
│   └── manifest.json
├── src/
│   ├── components/
│   │   ├── common/
│   │   │   ├── Button.tsx
│   │   │   ├── Input.tsx
│   │   │   ├── Modal.tsx
│   │   │   └── Loading.tsx
│   │   ├── layout/
│   │   │   ├── Header.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   └── Footer.tsx
│   │   └── features/
│   │       ├── tasks/
│   │       │   ├── TaskList.tsx
│   │       │   ├── TaskItem.tsx
│   │       │   └── TaskForm.tsx
│   │       └── users/
│   │           ├── UserList.tsx
│   │           └── UserCard.tsx
│   ├── pages/
│   │   ├── Dashboard.tsx
│   │   ├── Tasks.tsx
│   │   ├── Users.tsx
│   │   └── Settings.tsx
│   ├── hooks/
│   │   ├── useTasks.ts
│   │   ├── useUsers.ts
│   │   └── useAuth.ts
│   ├── store/
│   │   ├── store.ts
│   │   ├── slices/
│   │   │   ├── taskSlice.ts
│   │   │   ├── userSlice.ts
│   │   │   └── authSlice.ts
│   │   └── middleware/
│   │       └── logger.ts
│   ├── services/
│   │   ├── api.ts
│   │   ├── taskService.ts
│   │   └── userService.ts
│   ├── utils/
│   │   ├── formatDate.ts
│   │   ├── validate.ts
│   │   └── constants.ts
│   ├── types/
│   │   ├── task.ts
│   │   ├── user.ts
│   │   └── api.ts
│   ├── styles/
│   │   ├── global.css
│   │   └── variables.css
│   ├── App.tsx
│   ├── index.tsx
│   └── setupTests.ts
├── .env.development
├── .env.production
├── tsconfig.json
├── package.json
└── README.md
主应用组件(详细注释版)
typescript 复制代码
// src/App.tsx
// 导入React
import React, { Suspense } from 'react';
// 导入路由组件
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// 导入Redux Provider
import { Provider } from 'react-redux';
// 导入store
import { store } from './store/store';
// 导入布局组件
import Layout from './components/layout/Layout';
// 导入页面组件(懒加载)
import Dashboard from './pages/Dashboard';
// 导入加载组件
import Loading from './components/common/Loading';
// 导入样式
import './styles/global.css';

// 定义主应用组件
function App() {
  return (
    // 使用Provider包装整个应用,提供Redux store
    <Provider store={store}>
      {/* 使用BrowserRouter提供路由功能 */}
      <BrowserRouter>
        {/* 使用Suspense包装路由,处理懒加载组件的加载状态 */}
        <Suspense fallback={<Loading />}>
          {/* 使用Routes定义路由规则 */}
          <Routes>
            {/* 使用Layout作为布局组件 */}
            <Route path="/" element={<Layout />}>
              {/* 定义各个页面的路由 */}
              <Route index element={<Dashboard />} />
              <Route path="tasks" element={<Tasks />} />
              <Route path="users" element={<Users />} />
              <Route path="settings" element={<Settings />} />
            </Route>
          </Routes>
        </Suspense>
      </BrowserRouter>
    </Provider>
  );
}

// 懒加载Tasks页面
const Tasks = React.lazy(() => import('./pages/Tasks'));

// 懒加载Users页面
const Users = React.lazy(() => import('./pages/Users'));

// 懒加载Settings页面
const Settings = React.lazy(() => import('./pages/Settings'));

// 导出App组件
export default App;
任务管理页面(详细注释版)
typescript 复制代码
// src/pages/Tasks.tsx
// 导入React和useState
import React, { useState, useMemo, useCallback } from 'react';
// 导入Redux hooks
import { useAppSelector, useAppDispatch } from '../store/store';
// 导入actions
import { addTask, updateTask, deleteTask, toggleTask } from '../store/slices/taskSlice';
// 导入组件
import TaskList from '../components/features/tasks/TaskList';
import TaskForm from '../components/features/tasks/TaskForm';
import Button from '../components/common/Button';
import Modal from '../components/common/Modal';
// 导入类型
import { Task } from '../types/task';

// 定义Tasks页面组件
function Tasks() {
  // 使用Redux hooks获取状态和dispatch函数
  const tasks = useAppSelector(state => state.tasks.items);
  const dispatch = useAppDispatch();
  
  // 使用useState Hook管理本地状态
  const [showForm, setShowForm] = useState(false);
  const [editingTask, setEditingTask] = useState<Task | null>(null);
  const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');

  // 使用useMemo Hook记忆化过滤后的任务
  const filteredTasks = useMemo(() => {
    switch (filter) {
      case 'active':
        return tasks.filter(task => !task.completed);
      case 'completed':
        return tasks.filter(task => task.completed);
      default:
        return tasks;
    }
  }, [tasks, filter]);

  // 使用useCallback Hook记忆化处理函数
  const handleAddTask = useCallback((taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>) => {
    dispatch(addTask(taskData));
    setShowForm(false);
  }, [dispatch]);

  const handleUpdateTask = useCallback((id: string, updates: Partial<Task>) => {
    dispatch(updateTask({ id, updates }));
    setEditingTask(null);
    setShowForm(false);
  }, [dispatch]);

  const handleDeleteTask = useCallback((id: string) => {
    if (window.confirm('Are you sure you want to delete this task?')) {
      dispatch(deleteTask(id));
    }
  }, [dispatch]);

  const handleToggleTask = useCallback((id: string) => {
    dispatch(toggleTask(id));
  }, [dispatch]);

  const handleEditTask = useCallback((task: Task) => {
    setEditingTask(task);
    setShowForm(true);
  }, []);

  const handleCloseForm = useCallback(() => {
    setEditingTask(null);
    setShowForm(false);
  }, []);

  return (
    <div className="tasks-page">
      <div className="tasks-header">
        <h1>Task Management</h1>
        <Button onClick={() => setShowForm(true)}>
          Add New Task
        </Button>
      </div>

      <div className="tasks-filters">
        <Button
          variant={filter === 'all' ? 'primary' : 'secondary'}
          onClick={() => setFilter('all')}
        >
          All
        </Button>
        <Button
          variant={filter === 'active' ? 'primary' : 'secondary'}
          onClick={() => setFilter('active')}
        >
          Active
        </Button>
        <Button
          variant={filter === 'completed' ? 'primary' : 'secondary'}
          onClick={() => setFilter('completed')}
        >
          Completed
        </Button>
      </div>

      <TaskList
        tasks={filteredTasks}
        onEdit={handleEditTask}
        onDelete={handleDeleteTask}
        onToggle={handleToggleTask}
      />

      {showForm && (
        <Modal onClose={handleCloseForm}>
          <TaskForm
            task={editingTask}
            onSubmit={editingTask ? 
              (data) => handleUpdateTask(editingTask.id, data) : 
              handleAddTask
            }
            onCancel={handleCloseForm}
          />
        </Modal>
      )}
    </div>
  );
}

// 导出Tasks组件
export default Tasks;
API服务层(详细注释版)
typescript 复制代码
// src/services/api.ts
// 定义API响应接口
// 使用泛型T表示响应数据的类型
interface ApiResponse<T> {
  // 响应数据
  data: T;
  
  // 响应状态
  status: number;
  
  // 响应消息
  message?: string;
}

// 定义API错误接口
interface ApiError {
  // 错误消息
  message: string;
  
  // 错误代码
  code?: string;
  
  // 错误详情
  details?: any;
}

// 定义API配置
const API_CONFIG = {
  // API基础URL
  baseURL: process.env.REACT_APP_API_URL || 'http://localhost:8080/api',
  
  // 请求超时时间(毫秒)
  timeout: 10000,
  
  // 默认请求头
  headers: {
    'Content-Type': 'application/json'
  }
};

// 定义API客户端类
class ApiClient {
  // 私有方法:发送请求
  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<ApiResponse<T>> {
    // 构建完整URL
    const url = `${API_CONFIG.baseURL}${endpoint}`;
    
    // 合并请求头
    const headers = {
      ...API_CONFIG.headers,
      ...options.headers
    };
    
    // 添加认证token(如果存在)
    const token = localStorage.getItem('token');
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
    
    try {
      // 创建AbortController用于取消请求
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), API_CONFIG.timeout);
      
      // 发送请求
      const response = await fetch(url, {
        ...options,
        headers,
        signal: controller.signal
      });
      
      // 清除超时定时器
      clearTimeout(timeoutId);
      
      // 检查响应状态
      if (!response.ok) {
        const error: ApiError = await response.json();
        throw new Error(error.message || 'Request failed');
      }
      
      // 解析响应数据
      const data: T = await response.json();
      
      return {
        data,
        status: response.status
      };
    } catch (error) {
      // 处理错误
      if (error instanceof Error) {
        throw new Error(error.message);
      }
      throw new Error('An unknown error occurred');
    }
  }
  
  // GET请求方法
  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'GET'
    });
  }
  
  // POST请求方法
  async post<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  // PUT请求方法
  async put<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  // DELETE请求方法
  async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'DELETE'
    });
  }
}

// 创建API客户端实例
const apiClient = new ApiClient();

// 导出API客户端
export default apiClient;
任务服务(详细注释版)
typescript 复制代码
// src/services/taskService.ts
// 导入API客户端
import apiClient from './api';
// 导入类型
import { Task } from '../types/task';

// 定义任务服务类
class TaskService {
  // 获取所有任务
  async getAllTasks(): Promise<Task[]> {
    const response = await apiClient.get<Task[]>('/tasks');
    return response.data;
  }
  
  // 根据ID获取任务
  async getTaskById(id: string): Promise<Task> {
    const response = await apiClient.get<Task>(`/tasks/${id}`);
    return response.data;
  }
  
  // 创建任务
  async createTask(taskData: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
    const response = await apiClient.post<Task>('/tasks', taskData);
    return response.data;
  }
  
  // 更新任务
  async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
    const response = await apiClient.put<Task>(`/tasks/${id}`, updates);
    return response.data;
  }
  
  // 删除任务
  async deleteTask(id: string): Promise<void> {
    await apiClient.delete<void>(`/tasks/${id}`);
  }
  
  // 切换任务完成状态
  async toggleTask(id: string): Promise<Task> {
    const task = await this.getTaskById(id);
    return this.updateTask(id, { completed: !task.completed });
  }
}

// 创建任务服务实例
const taskService = new TaskService();

// 导出任务服务
export default taskService;
类型定义(详细注释版)
typescript 复制代码
// src/types/task.ts
// 定义任务优先级枚举
export enum TaskPriority {
  LOW = 'low',
  MEDIUM = 'medium',
  HIGH = 'high',
  URGENT = 'urgent'
}

// 定义任务状态枚举
export enum TaskStatus {
  PENDING = 'pending',
  IN_PROGRESS = 'in_progress',
  COMPLETED = 'completed',
  CANCELLED = 'cancelled'
}

// 定义任务接口
export interface Task {
  // 任务ID
  id: string;
  
  // 任务标题
  title: string;
  
  // 任务描述
  description?: string;
  
  // 任务优先级
  priority: TaskPriority;
  
  // 任务状态
  status: TaskStatus;
  
  // 是否完成
  completed: boolean;
  
  // 截止日期
  dueDate?: string;
  
  // 创建时间
  createdAt: string;
  
  // 更新时间
  updatedAt: string;
  
  // 创建者ID
  createdBy: string;
  
  // 分配给的用户ID
  assignedTo?: string;
  
  // 标签
  tags?: string[];
}

// 定义任务创建数据接口
export interface CreateTaskData {
  title: string;
  description?: string;
  priority: TaskPriority;
  dueDate?: string;
  assignedTo?: string;
  tags?: string[];
}

// 定义任务更新数据接口
export interface UpdateTaskData {
  title?: string;
  description?: string;
  priority?: TaskPriority;
  status?: TaskStatus;
  completed?: boolean;
  dueDate?: string;
  assignedTo?: string;
  tags?: string[];
}
任务列表组件(详细注释版)
typescript 复制代码
// src/components/features/tasks/TaskList.tsx
// 导入React和memo
import React, { memo } from 'react';
// 导入组件
import TaskItem from './TaskItem';
// 导入类型
import { Task } from '../../../types/task';

// 定义TaskList组件的Props接口
interface TaskListProps {
  // 任务列表
  tasks: Task[];
  
  // 编辑任务处理函数
  onEdit: (task: Task) => void;
  
  // 删除任务处理函数
  onDelete: (id: string) => void;
  
  // 切换任务完成状态处理函数
  onToggle: (id: string) => void;
}

// 定义TaskList组件
// 使用memo优化,只有当props变化时才重新渲染
const TaskList: React.FC<TaskListProps> = memo(function TaskList({
  tasks,
  onEdit,
  onDelete,
  onToggle
}) {
  // 如果任务列表为空,显示空状态
  if (tasks.length === 0) {
    return (
      <div className="task-list-empty">
        <p>No tasks found</p>
      </div>
    );
  }

  return (
    <div className="task-list">
      {tasks.map(task => (
        <TaskItem
          key={task.id}
          task={task}
          onEdit={onEdit}
          onDelete={onDelete}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
});

// 导出TaskList组件
export default TaskList;
任务项组件(详细注释版)
typescript 复制代码
// src/components/features/tasks/TaskItem.tsx
// 导入React和memo
import React, { memo } from 'react';
// 导入组件
import Button from '../../common/Button';
// 导入类型
import { Task } from '../../../types/task';

// 定义TaskItem组件的Props接口
interface TaskItemProps {
  // 任务数据
  task: Task;
  
  // 编辑任务处理函数
  onEdit: (task: Task) => void;
  
  // 删除任务处理函数
  onDelete: (id: string) => void;
  
  // 切换任务完成状态处理函数
  onToggle: (id: string) => void;
}

// 定义TaskItem组件
// 使用memo优化,自定义比较函数
const TaskItem: React.FC<TaskItemProps> = memo(function TaskItem({
  task,
  onEdit,
  onDelete,
  onToggle
}) {
  // 处理编辑点击
  const handleEdit = () => {
    onEdit(task);
  };

  // 处理删除点击
  const handleDelete = () => {
    onDelete(task.id);
  };

  // 处理切换完成状态
  const handleToggle = () => {
    onToggle(task.id);
  };

  // 格式化日期
  const formatDate = (dateString?: string) => {
    if (!dateString) return '';
    return new Date(dateString).toLocaleDateString();
  };

  return (
    <div className={`task-item ${task.completed ? 'completed' : ''} priority-${task.priority}`}>
      <div className="task-item-header">
        <div className="task-item-title">
          <input
            type="checkbox"
            checked={task.completed}
            onChange={handleToggle}
          />
          <h3>{task.title}</h3>
        </div>
        <div className="task-item-actions">
          <Button variant="secondary" size="small" onClick={handleEdit}>
            Edit
          </Button>
          <Button variant="danger" size="small" onClick={handleDelete}>
            Delete
          </Button>
        </div>
      </div>
      
      {task.description && (
        <p className="task-item-description">{task.description}</p>
      )}
      
      <div className="task-item-meta">
        <span className="task-priority">{task.priority}</span>
        <span className="task-status">{task.status}</span>
        {task.dueDate && (
          <span className="task-due-date">Due: {formatDate(task.dueDate)}</span>
        )}
      </div>
    </div>
  );
}, (prevProps, nextProps) => {
  // 自定义比较函数
  // 只有当任务数据变化时才重新渲染
  return prevProps.task.id === nextProps.task.id &&
         JSON.stringify(prevProps.task) === JSON.stringify(nextProps.task);
});

// 导出TaskItem组件
export default TaskItem;
任务表单组件(详细注释版)
typescript 复制代码
// src/components/features/tasks/TaskForm.tsx
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 导入组件
import Button from '../../common/Button';
import Input from '../../common/Input';
// 导入类型
import { Task, TaskPriority, CreateTaskData, UpdateTaskData } from '../../../types/task';

// 定义TaskForm组件的Props接口
interface TaskFormProps {
  // 编辑的任务(如果存在)
  task?: Task | null;
  
  // 提交处理函数
  onSubmit: (data: CreateTaskData | UpdateTaskData) => void;
  
  // 取消处理函数
  onCancel: () => void;
}

// 定义TaskForm组件
const TaskForm: React.FC<TaskFormProps> = ({ task, onSubmit, onCancel }) => {
  // 使用useState Hook管理表单状态
  const [formData, setFormData] = useState<CreateTaskData>({
    title: '',
    description: '',
    priority: TaskPriority.MEDIUM,
    dueDate: '',
    tags: []
  });

  // 当编辑任务时,更新表单数据
  useEffect(() => {
    if (task) {
      setFormData({
        title: task.title,
        description: task.description || '',
        priority: task.priority,
        dueDate: task.dueDate || '',
        tags: task.tags || []
      });
    }
  }, [task]);

  // 处理输入变化
  const handleChange = (field: keyof CreateTaskData, value: any) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  };

  // 处理表单提交
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    
    // 验证表单
    if (!formData.title.trim()) {
      alert('Title is required');
      return;
    }
    
    // 提交表单数据
    onSubmit(formData);
  };

  return (
    <form className="task-form" onSubmit={handleSubmit}>
      <div className="form-group">
        <label htmlFor="title">Title *</label>
        <Input
          id="title"
          type="text"
          value={formData.title}
          onChange={(e) => handleChange('title', e.target.value)}
          required
        />
      </div>
      
      <div className="form-group">
        <label htmlFor="description">Description</label>
        <textarea
          id="description"
          value={formData.description}
          onChange={(e) => handleChange('description', e.target.value)}
          rows={4}
        />
      </div>
      
      <div className="form-group">
        <label htmlFor="priority">Priority</label>
        <select
          id="priority"
          value={formData.priority}
          onChange={(e) => handleChange('priority', e.target.value as TaskPriority)}
        >
          <option value={TaskPriority.LOW}>Low</option>
          <option value={TaskPriority.MEDIUM}>Medium</option>
          <option value={TaskPriority.HIGH}>High</option>
          <option value={TaskPriority.URGENT}>Urgent</option>
        </select>
      </div>
      
      <div className="form-group">
        <label htmlFor="dueDate">Due Date</label>
        <Input
          id="dueDate"
          type="date"
          value={formData.dueDate}
          onChange={(e) => handleChange('dueDate', e.target.value)}
        />
      </div>
      
      <div className="form-actions">
        <Button type="submit" variant="primary">
          {task ? 'Update Task' : 'Create Task'}
        </Button>
        <Button type="button" variant="secondary" onClick={onCancel}>
          Cancel
        </Button>
      </div>
    </form>
  );
};

// 导出TaskForm组件
export default TaskForm;
样式文件(详细注释版)
css 复制代码
/* src/styles/global.css */
/* 全局样式重置 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* 根元素样式 */
html {
  font-size: 16px;
  line-height: 1.5;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #f5f5f5;
  color: #333;
}

/* 代码字体 */
code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

/* 链接样式 */
a {
  color: #007bff;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* 按钮基础样式 */
button {
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
  border: none;
  outline: none;
}

/* 输入框基础样式 */
input, textarea, select {
  font-family: inherit;
  font-size: inherit;
  outline: none;
}

/* 列表样式 */
ul, ol {
  list-style: none;
}

/* 任务页面样式 */
.tasks-page {
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
}

.tasks-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.tasks-header h1 {
  font-size: 2rem;
  color: #333;
}

.tasks-filters {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
}

.task-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.task-list-empty {
  text-align: center;
  padding: 4rem 2rem;
  color: #666;
}

.task-item {
  background-color: white;
  border-radius: 8px;
  padding: 1.5rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  transition: box-shadow 0.2s;
}

.task-item:hover {
  box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}

.task-item.completed {
  opacity: 0.7;
}

.task-item.completed .task-item-title h3 {
  text-decoration: line-through;
  color: #999;
}

.task-item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.task-item-title {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex: 1;
}

.task-item-title input[type="checkbox"] {
  width: 20px;
  height: 20px;
  cursor: pointer;
}

.task-item-title h3 {
  margin: 0;
  font-size: 1.2rem;
  color: #333;
}

.task-item-actions {
  display: flex;
  gap: 0.5rem;
}

.task-item-description {
  margin-bottom: 1rem;
  color: #666;
  line-height: 1.6;
}

.task-item-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.9rem;
  color: #999;
}

.task-priority {
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-weight: 500;
}

.task-priority.priority-low {
  background-color: #d1ecf1;
  color: #0c5460;
}

.task-priority.priority-medium {
  background-color: #fff3cd;
  color: #856404;
}

.task-priority.priority-high {
  background-color: #f8d7da;
  color: #721c24;
}

.task-priority.priority-urgent {
  background-color: #dc3545;
  color: white;
}

/* 任务表单样式 */
.task-form {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  padding: 2rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.form-group label {
  font-weight: 500;
  color: #333;
}

.form-group input,
.form-group textarea,
.form-group select {
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
  border-color: #007bff;
  box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
}

.form-actions {
  display: flex;
  gap: 1rem;
  justify-content: flex-end;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .tasks-page {
    padding: 1rem;
  }
  
  .tasks-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }
  
  .task-item-header {
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
  }
  
  .task-item-actions {
    justify-content: flex-end;
  }
}
相关推荐
代码i小学生3 小时前
c#异步学习记录
学习
阿蔹3 小时前
UI测试自动化--Web--Python_Selenium-元素定位
前端·ui·自动化
先生沉默先3 小时前
c#Socket学习,使用Socket创建一个在线聊天,服务端功能实现,(3)
服务器·学习·c#
万少3 小时前
【鸿蒙心迹】-03-自然壁纸实战教程-项目结构介绍
前端
万少3 小时前
【鸿蒙心迹】- 02-自然壁纸实战教程-AGC 新建项目
前端
xwz小王子3 小时前
IROS 2025论文分享:基于大语言模型与行为树的人机交互学习实现自适应机器人操作
学习·语言模型·人机交互
嵌入式×边缘AI:打怪升级日志4 小时前
USB协议详解:从物理连接到数据传输的完整解析
网络·学习·usb
南望无一4 小时前
Vite拆包后Chunk级别的循环依赖分析及解决方案
前端·vite
快乐星球喂4 小时前
子组件和父组件之间优雅通信---松耦合
前端·vue.js