学习目标
- 掌握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;
}
}