学习目标
- 理解微前端架构
- 掌握国际化实现
- 学会无障碍访问开发
- 理解错误处理和监控
- 构建完整的企业级应用
学习时间安排
总时长:8-9小时
- 微前端架构:2小时
- 国际化实现:1.5小时
- 无障碍访问:1.5小时
- 错误处理和监控:1.5小时
- 完整项目实践:2-3小时
第一部分:微前端架构 (2小时)
1.1 微前端概念
微前端基础(详细注释版)
javascript
// 微前端是一种架构模式,将前端应用拆分为多个独立的小型应用
// 每个微前端应用可以独立开发、部署和运行
// 微前端的优势:
// 1. 独立开发:不同团队可以独立开发不同的微前端
// 2. 独立部署:每个微前端可以独立部署,不影响其他应用
// 3. 技术栈自由:不同的微前端可以使用不同的技术栈
// 4. 代码隔离:每个微前端的代码相互隔离,避免冲突
// 微前端的实现方式:
// 1. 路由分发:通过路由将不同的请求分发到不同的微前端
// 2. 组件加载:动态加载不同微前端的组件
// 3. iframe:使用iframe嵌入不同的微前端
// 4. Web Components:使用Web Components技术
// 基础微前端容器实现
// src/microfrontend/container.js
class MicroFrontendContainer {
constructor() {
// 存储已加载的微前端
this.loadedApps = new Map();
// 存储微前端配置
this.appConfigs = new Map();
}
// 注册微前端应用
registerApp(name, config) {
this.appConfigs.set(name, {
entry: config.entry,
container: config.container,
activeRule: config.activeRule,
...config
});
}
// 加载微前端应用
async loadApp(name) {
// 如果已经加载,直接返回
if (this.loadedApps.has(name)) {
return this.loadedApps.get(name);
}
const config = this.appConfigs.get(name);
if (!config) {
throw new Error(`App ${name} not registered`);
}
try {
// 动态加载微前端的入口文件
const module = await import(config.entry);
// 初始化微前端应用
const app = {
name,
module,
mount: module.mount,
unmount: module.unmount,
config
};
// 缓存已加载的应用
this.loadedApps.set(name, app);
return app;
} catch (error) {
console.error(`Failed to load app ${name}:`, error);
throw error;
}
}
// 挂载微前端应用
async mountApp(name, containerElement) {
const app = await this.loadApp(name);
if (app.mount && typeof app.mount === 'function') {
await app.mount(containerElement);
}
}
// 卸载微前端应用
async unmountApp(name) {
const app = this.loadedApps.get(name);
if (app && app.unmount && typeof app.unmount === 'function') {
await app.unmount();
}
}
// 检查路由是否匹配
isActive(name, path) {
const config = this.appConfigs.get(name);
if (!config || !config.activeRule) {
return false;
}
if (typeof config.activeRule === 'string') {
return path.startsWith(config.activeRule);
}
if (typeof config.activeRule === 'function') {
return config.activeRule(path);
}
if (config.activeRule instanceof RegExp) {
return config.activeRule.test(path);
}
return false;
}
}
// 创建全局微前端容器实例
const microFrontendContainer = new MicroFrontendContainer();
// 导出容器
export default microFrontendContainer;
1.2 微前端路由集成
路由集成实现(详细注释版)
javascript
// src/microfrontend/router.js
// 导入React和路由
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
// 导入微前端容器
import microFrontendContainer from './container';
// 定义微前端路由组件
function MicroFrontendRoute({ appName, containerId }) {
// 使用useLocation Hook获取当前路径
const location = useLocation();
// 使用useState Hook管理加载状态
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 获取容器元素
const container = document.getElementById(containerId);
if (!container) {
setError(`Container ${containerId} not found`);
setLoading(false);
return;
}
// 挂载微前端应用
const mountApp = async () => {
try {
setLoading(true);
setError(null);
await microFrontendContainer.mountApp(appName, container);
setLoading(false);
} catch (err) {
console.error(`Failed to mount app ${appName}:`, err);
setError(err.message);
setLoading(false);
}
};
mountApp();
// 清理函数:卸载微前端应用
return () => {
microFrontendContainer.unmountApp(appName).catch(err => {
console.error(`Failed to unmount app ${appName}:`, err);
});
};
}, [appName, containerId, location.pathname]);
if (error) {
return (
<div className="microfrontend-error">
<h2>Error Loading {appName}</h2>
<p>{error}</p>
</div>
);
}
if (loading) {
return (
<div className="microfrontend-loading">
<div className="spinner"></div>
<p>Loading {appName}...</p>
</div>
);
}
return <div id={containerId} className="microfrontend-container" />;
}
// 定义微前端路由配置组件
function MicroFrontendRouter() {
const location = useLocation();
const [activeApp, setActiveApp] = useState(null);
useEffect(() => {
// 查找匹配的微前端应用
const findActiveApp = () => {
for (const [name, config] of microFrontendContainer.appConfigs) {
if (microFrontendContainer.isActive(name, location.pathname)) {
return name;
}
}
return null;
};
const app = findActiveApp();
setActiveApp(app);
}, [location.pathname]);
if (!activeApp) {
return (
<div className="no-app-found">
<h2>No Micro Frontend App Found</h2>
<p>No micro frontend app is configured for this route.</p>
</div>
);
}
return (
<MicroFrontendRoute
appName={activeApp}
containerId={`microfrontend-${activeApp}`}
/>
);
}
// 导出组件
export { MicroFrontendRoute, MicroFrontendRouter };
第二部分:国际化实现 (1.5小时)
2.1 React国际化基础
安装i18next(详细注释版)
bash
# 安装react-i18next和i18next
npm install react-i18next i18next i18next-browser-languagedetector
i18next配置(详细注释版)
javascript
// src/i18n/config.js
// 导入i18next
import i18n from 'i18next';
// 导入语言检测器
import LanguageDetector from 'i18next-browser-languagedetector';
// 导入React i18next
import { initReactI18next } from 'react-i18next';
// 导入翻译资源
import translationEN from './locales/en/translation.json';
import translationZH from './locales/zh/translation.json';
// 配置i18next
i18n
// 使用语言检测器
.use(LanguageDetector)
// 使用React i18next
.use(initReactI18next)
// 初始化配置
.init({
// 资源文件
resources: {
en: {
translation: translationEN
},
zh: {
translation: translationZH
}
},
// 默认语言
fallbackLng: 'en',
// 调试模式
debug: process.env.NODE_ENV === 'development',
// 插值配置
interpolation: {
escapeValue: false // React已经转义了
},
// 语言检测配置
detection: {
// 检测顺序
order: ['localStorage', 'navigator', 'htmlTag'],
// 缓存用户选择的语言
caches: ['localStorage']
}
});
// 导出i18n实例
export default i18n;
翻译资源文件(详细注释版)
json
// src/i18n/locales/en/translation.json
{
"common": {
"welcome": "Welcome",
"hello": "Hello",
"goodbye": "Goodbye",
"loading": "Loading...",
"error": "Error",
"success": "Success",
"cancel": "Cancel",
"confirm": "Confirm",
"save": "Save",
"delete": "Delete",
"edit": "Edit",
"create": "Create",
"search": "Search",
"filter": "Filter",
"sort": "Sort"
},
"navigation": {
"home": "Home",
"about": "About",
"contact": "Contact",
"blog": "Blog",
"dashboard": "Dashboard"
},
"user": {
"profile": "Profile",
"settings": "Settings",
"logout": "Logout",
"login": "Login",
"register": "Register",
"name": "Name",
"email": "Email",
"password": "Password"
},
"task": {
"title": "Task Management",
"addTask": "Add Task",
"editTask": "Edit Task",
"deleteTask": "Delete Task",
"completeTask": "Complete Task",
"taskTitle": "Task Title",
"taskDescription": "Task Description",
"taskPriority": "Priority",
"taskDueDate": "Due Date",
"taskStatus": "Status",
"allTasks": "All Tasks",
"activeTasks": "Active Tasks",
"completedTasks": "Completed Tasks"
}
}
json
// src/i18n/locales/zh/translation.json
{
"common": {
"welcome": "欢迎",
"hello": "你好",
"goodbye": "再见",
"loading": "加载中...",
"error": "错误",
"success": "成功",
"cancel": "取消",
"confirm": "确认",
"save": "保存",
"delete": "删除",
"edit": "编辑",
"create": "创建",
"search": "搜索",
"filter": "筛选",
"sort": "排序"
},
"navigation": {
"home": "首页",
"about": "关于",
"contact": "联系",
"blog": "博客",
"dashboard": "仪表盘"
},
"user": {
"profile": "个人资料",
"settings": "设置",
"logout": "登出",
"login": "登录",
"register": "注册",
"name": "姓名",
"email": "邮箱",
"password": "密码"
},
"task": {
"title": "任务管理",
"addTask": "添加任务",
"editTask": "编辑任务",
"deleteTask": "删除任务",
"completeTask": "完成任务",
"taskTitle": "任务标题",
"taskDescription": "任务描述",
"taskPriority": "优先级",
"taskDueDate": "截止日期",
"taskStatus": "状态",
"allTasks": "所有任务",
"activeTasks": "活跃任务",
"completedTasks": "已完成任务"
}
}
2.2 使用国际化
国际化组件(详细注释版)
typescript
// src/components/LanguageSwitcher.tsx
// 导入React
import React from 'react';
// 导入i18next Hook
import { useTranslation } from 'react-i18next';
// 定义LanguageSwitcher组件
function LanguageSwitcher() {
// 使用useTranslation Hook获取翻译函数和当前语言
const { i18n } = useTranslation();
// 处理语言切换
const handleLanguageChange = (language: string) => {
i18n.changeLanguage(language);
};
// 获取支持的语言列表
const languages = [
{ code: 'en', name: 'English' },
{ code: 'zh', name: '中文' }
];
return (
<div className="language-switcher">
<select
value={i18n.language}
onChange={(e) => handleLanguageChange(e.target.value)}
className="language-select"
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.name}
</option>
))}
</select>
</div>
);
}
// 导出LanguageSwitcher组件
export default LanguageSwitcher;
使用翻译的组件(详细注释版)
typescript
// src/components/TranslatedComponent.tsx
// 导入React
import React from 'react';
// 导入i18next Hook
import { useTranslation } from 'react-i18next';
// 定义TranslatedComponent组件
function TranslatedComponent() {
// 使用useTranslation Hook获取翻译函数
const { t } = useTranslation();
return (
<div className="translated-component">
<h1>{t('common.welcome')}</h1>
<p>{t('common.hello')}</p>
<nav>
<ul>
<li>{t('navigation.home')}</li>
<li>{t('navigation.about')}</li>
<li>{t('navigation.contact')}</li>
</ul>
</nav>
<div>
<h2>{t('task.title')}</h2>
<button>{t('task.addTask')}</button>
<button>{t('task.editTask')}</button>
<button>{t('task.deleteTask')}</button>
</div>
{/* 使用插值 */}
<p>{t('user.welcome', { name: 'John' })}</p>
{/* 使用复数形式 */}
<p>{t('task.count', { count: 5 })}</p>
</div>
);
}
// 导出TranslatedComponent组件
export default TranslatedComponent;
第三部分:无障碍访问 (1.5小时)
3.1 ARIA属性
无障碍组件(详细注释版)
typescript
// src/components/accessible/Button.tsx
// 导入React
import React from 'react';
// 定义Button组件的Props接口
interface ButtonProps {
// 按钮文本
children: React.ReactNode;
// 点击事件处理函数
onClick?: () => void;
// 按钮类型
type?: 'button' | 'submit' | 'reset';
// 是否禁用
disabled?: boolean;
// 自定义样式类名
className?: string;
// ARIA标签
'aria-label'?: string;
// ARIA描述
'aria-describedby'?: string;
// 是否按下状态
'aria-pressed'?: boolean;
}
// 定义无障碍Button组件
const AccessibleButton: React.FC<ButtonProps> = ({
children,
onClick,
type = 'button',
disabled = false,
className = '',
'aria-label': ariaLabel,
'aria-describedby': ariaDescribedBy,
'aria-pressed': ariaPressed
}) => {
return (
<button
type={type}
onClick={onClick}
disabled={disabled}
className={className}
aria-label={ariaLabel}
aria-describedby={ariaDescribedBy}
aria-pressed={ariaPressed}
// 键盘导航支持
onKeyDown={(e) => {
// 支持Enter和Space键触发点击
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (onClick && !disabled) {
onClick();
}
}
}}
>
{children}
</button>
);
};
// 导出AccessibleButton组件
export default AccessibleButton;
无障碍表单组件(详细注释版)
typescript
// src/components/accessible/Form.tsx
// 导入React
import React, { useState } from 'react';
// 定义Form组件的Props接口
interface FormProps {
// 提交处理函数
onSubmit: (data: FormData) => void;
// 表单字段
fields: FormField[];
// 提交按钮文本
submitText?: string;
}
// 定义表单字段接口
interface FormField {
// 字段名称
name: string;
// 字段标签
label: string;
// 字段类型
type: 'text' | 'email' | 'password' | 'textarea' | 'select';
// 是否必需
required?: boolean;
// 错误消息
error?: string;
// 帮助文本
helpText?: string;
// 选项(用于select类型)
options?: Array<{ value: string; label: string }>;
}
// 定义无障碍Form组件
const AccessibleForm: React.FC<FormProps> = ({
onSubmit,
fields,
submitText = 'Submit'
}) => {
// 使用useState Hook管理表单数据
const [formData, setFormData] = useState<Record<string, string>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
// 处理输入变化
const handleChange = (name: string, value: string) => {
setFormData(prev => ({
...prev,
[name]: value
}));
// 清除该字段的错误
if (errors[name]) {
setErrors(prev => {
const newErrors = { ...prev };
delete newErrors[name];
return newErrors;
});
}
};
// 处理表单提交
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// 验证表单
const newErrors: Record<string, string> = {};
fields.forEach(field => {
if (field.required && !formData[field.name]) {
newErrors[field.name] = `${field.label} is required`;
}
});
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
// 提交表单数据
const formDataObj = new FormData();
Object.entries(formData).forEach(([key, value]) => {
formDataObj.append(key, value);
});
onSubmit(formDataObj);
};
return (
<form onSubmit={handleSubmit} className="accessible-form">
{fields.map(field => {
const fieldId = `field-${field.name}`;
const errorId = `error-${field.name}`;
const helpId = `help-${field.name}`;
const hasError = !!errors[field.name];
return (
<div key={field.name} className="form-field">
<label
htmlFor={fieldId}
className={field.required ? 'required' : ''}
>
{field.label}
{field.required && (
<span className="required-indicator" aria-label="required">
*
</span>
)}
</label>
{field.type === 'textarea' ? (
<textarea
id={fieldId}
name={field.name}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
required={field.required}
aria-invalid={hasError}
aria-describedby={
hasError
? errorId
: field.helpText
? helpId
: undefined
}
className={hasError ? 'error' : ''}
/>
) : field.type === 'select' ? (
<select
id={fieldId}
name={field.name}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
required={field.required}
aria-invalid={hasError}
aria-describedby={
hasError
? errorId
: field.helpText
? helpId
: undefined
}
className={hasError ? 'error' : ''}
>
<option value="">Select {field.label}</option>
{field.options?.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : (
<input
id={fieldId}
name={field.name}
type={field.type}
value={formData[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
required={field.required}
aria-invalid={hasError}
aria-describedby={
hasError
? errorId
: field.helpText
? helpId
: undefined
}
className={hasError ? 'error' : ''}
/>
)}
{hasError && (
<div
id={errorId}
className="error-message"
role="alert"
aria-live="polite"
>
{errors[field.name]}
</div>
)}
{field.helpText && !hasError && (
<div id={helpId} className="help-text">
{field.helpText}
</div>
)}
</div>
);
})}
<button type="submit" className="submit-button">
{submitText}
</button>
</form>
);
};
// 导出AccessibleForm组件
export default AccessibleForm;
第四部分:错误处理和监控 (1.5小时)
4.1 错误边界
错误边界组件(详细注释版)
typescript
// src/components/ErrorBoundary.tsx
// 导入React
import React, { Component, ErrorInfo, ReactNode } from 'react';
// 定义ErrorBoundary的Props接口
interface ErrorBoundaryProps {
// 子组件
children: ReactNode;
// 错误回调函数
onError?: (error: Error, errorInfo: ErrorInfo) => void;
// 错误回退UI
fallback?: ReactNode;
}
// 定义ErrorBoundary的State接口
interface ErrorBoundaryState {
// 是否有错误
hasError: boolean;
// 错误对象
error: Error | null;
// 错误信息
errorInfo: ErrorInfo | null;
}
// 定义ErrorBoundary类组件
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
// 初始化状态
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
// 静态方法:从错误中获取状态
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return {
hasError: true,
error
};
}
// 生命周期方法:捕获错误
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// 更新状态
this.setState({
error,
errorInfo
});
// 调用错误回调
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
// 记录错误到错误监控服务
this.logErrorToService(error, errorInfo);
}
// 记录错误到监控服务
logErrorToService(error: Error, errorInfo: ErrorInfo) {
// 在实际应用中,这里会发送错误到监控服务(如Sentry)
console.error('Error caught by boundary:', error, errorInfo);
// 示例:发送到错误监控服务
if (process.env.NODE_ENV === 'production') {
// 发送错误到监控服务
// Sentry.captureException(error, { contexts: { react: errorInfo } });
}
}
// 处理重置错误
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
render() {
// 如果有错误,显示错误UI
if (this.state.hasError) {
// 如果提供了自定义fallback,使用它
if (this.props.fallback) {
return this.props.fallback;
}
// 默认错误UI
return (
<div className="error-boundary">
<div className="error-boundary-content">
<h2>Something went wrong</h2>
<p>An error occurred while rendering this component.</p>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="error-details">
<summary>Error Details</summary>
<pre>{this.state.error.toString()}</pre>
{this.state.errorInfo && (
<pre>{this.state.errorInfo.componentStack}</pre>
)}
</details>
)}
<button onClick={this.handleReset} className="reset-button">
Try Again
</button>
</div>
</div>
);
}
// 正常渲染子组件
return this.props.children;
}
}
// 导出ErrorBoundary组件
export default ErrorBoundary;
4.2 错误监控
错误监控服务(详细注释版)
typescript
// src/services/errorMonitoring.ts
// 错误监控服务类
class ErrorMonitoringService {
private initialized: boolean = false;
// 初始化错误监控
init(config: {
dsn: string;
environment: string;
release?: string;
}) {
if (this.initialized) {
console.warn('Error monitoring already initialized');
return;
}
// 在实际应用中,这里会初始化错误监控服务(如Sentry)
// Sentry.init({
// dsn: config.dsn,
// environment: config.environment,
// release: config.release
// });
this.initialized = true;
console.log('Error monitoring initialized');
}
// 捕获异常
captureException(error: Error, context?: Record<string, any>) {
if (!this.initialized) {
console.error('Error monitoring not initialized');
return;
}
// 在实际应用中,这里会发送错误到监控服务
// Sentry.captureException(error, { extra: context });
console.error('Exception captured:', error, context);
}
// 捕获消息
captureMessage(message: string, level: 'info' | 'warning' | 'error' = 'info') {
if (!this.initialized) {
console.warn('Error monitoring not initialized');
return;
}
// 在实际应用中,这里会发送消息到监控服务
// Sentry.captureMessage(message, level);
console.log(`Message captured [${level}]:`, message);
}
// 设置用户上下文
setUser(user: { id: string; email?: string; username?: string }) {
if (!this.initialized) {
return;
}
// 在实际应用中,这里会设置用户信息
// Sentry.setUser(user);
console.log('User context set:', user);
}
// 设置额外上下文
setContext(key: string, context: Record<string, any>) {
if (!this.initialized) {
return;
}
// 在实际应用中,这里会设置额外上下文
// Sentry.setContext(key, context);
console.log(`Context set [${key}]:`, context);
}
// 添加面包屑(用于错误追踪)
addBreadcrumb(breadcrumb: {
message: string;
category?: string;
level?: 'info' | 'warning' | 'error';
data?: Record<string, any>;
}) {
if (!this.initialized) {
return;
}
// 在实际应用中,这里会添加面包屑
// Sentry.addBreadcrumb(breadcrumb);
console.log('Breadcrumb added:', breadcrumb);
}
}
// 创建全局错误监控服务实例
const errorMonitoringService = new ErrorMonitoringService();
// 导出错误监控服务
export default errorMonitoringService;
全局错误处理(详细注释版)
typescript
// src/utils/errorHandler.ts
// 导入错误监控服务
import errorMonitoringService from '../services/errorMonitoring';
// 定义错误类型
export enum ErrorType {
NETWORK = 'NETWORK',
VALIDATION = 'VALIDATION',
AUTHENTICATION = 'AUTHENTICATION',
AUTHORIZATION = 'AUTHORIZATION',
SERVER = 'SERVER',
UNKNOWN = 'UNKNOWN'
}
// 定义错误处理函数
export function handleError(error: unknown, context?: Record<string, any>) {
// 确定错误类型
let errorType = ErrorType.UNKNOWN;
let errorMessage = 'An unknown error occurred';
let errorDetails: any = null;
// 处理不同类型的错误
if (error instanceof Error) {
errorMessage = error.message;
errorDetails = {
name: error.name,
stack: error.stack,
message: error.message
};
// 根据错误名称确定错误类型
if (error.name === 'NetworkError' || error.message.includes('fetch')) {
errorType = ErrorType.NETWORK;
} else if (error.name === 'ValidationError') {
errorType = ErrorType.VALIDATION;
} else if (error.name === 'AuthenticationError') {
errorType = ErrorType.AUTHENTICATION;
} else if (error.name === 'AuthorizationError') {
errorType = ErrorType.AUTHORIZATION;
}
} else if (typeof error === 'string') {
errorMessage = error;
} else if (error && typeof error === 'object') {
errorDetails = error;
errorMessage = JSON.stringify(error);
}
// 构建错误上下文
const errorContext = {
type: errorType,
message: errorMessage,
details: errorDetails,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
...context
};
// 发送错误到监控服务
if (error instanceof Error) {
errorMonitoringService.captureException(error, errorContext);
} else {
errorMonitoringService.captureMessage(errorMessage, 'error');
}
// 在开发环境中,也在控制台输出错误
if (process.env.NODE_ENV === 'development') {
console.error('Error handled:', errorContext);
}
// 返回错误信息,供UI使用
return {
type: errorType,
message: errorMessage,
details: errorDetails
};
}
// 定义异步错误处理函数
export async function handleAsyncError<T>(
asyncFn: () => Promise<T>,
context?: Record<string, any>
): Promise<T | null> {
try {
return await asyncFn();
} catch (error) {
handleError(error, context);
return null;
}
}
第五部分:完整项目实践 (2-3小时)
项目:企业级管理系统
主应用组件(详细注释版)
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';
// 导入i18next Provider
import { I18nextProvider } from 'react-i18next';
// 导入store
import { store } from './store/store';
// 导入i18n配置
import i18n from './i18n/config';
// 导入组件
import ErrorBoundary from './components/ErrorBoundary';
import Layout from './components/layout/Layout';
import Loading from './components/common/Loading';
// 导入样式
import './styles/globals.css';
// 定义主应用组件
function App() {
return (
// 使用ErrorBoundary包装整个应用,捕获所有错误
<ErrorBoundary
onError={(error, errorInfo) => {
// 记录错误到监控服务
console.error('App error:', error, errorInfo);
}}
fallback={
<div className="app-error">
<h1>Application Error</h1>
<p>The application encountered an error. Please refresh the page.</p>
<button onClick={() => window.location.reload()}>
Refresh Page
</button>
</div>
}
>
{/* 使用I18nextProvider提供国际化支持 */}
<I18nextProvider i18n={i18n}>
{/* 使用Provider提供Redux store */}
<Provider store={store}>
{/* 使用BrowserRouter提供路由功能 */}
<BrowserRouter>
{/* 使用Suspense处理懒加载组件 */}
<Suspense fallback={<Loading />}>
{/* 使用Routes定义路由规则 */}
<Routes>
{/* 使用Layout作为布局组件 */}
<Route path="/" element={<Layout />}>
{/* 定义各个页面的路由 */}
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="tasks" element={<Tasks />} />
<Route path="users" element={<Users />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</Suspense>
</BrowserRouter>
</Provider>
</I18nextProvider>
</ErrorBoundary>
);
}
// 懒加载页面组件
const Home = React.lazy(() => import('./pages/Home'));
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Tasks = React.lazy(() => import('./pages/Tasks'));
const Users = React.lazy(() => import('./pages/Users'));
const Settings = React.lazy(() => import('./pages/Settings'));
// 导出App组件
export default App;
布局组件(详细注释版)
typescript
// src/components/layout/Layout.tsx
// 导入React
import React from 'react';
// 导入路由组件
import { Outlet } from 'react-router-dom';
// 导入组件
import Header from './Header';
import Sidebar from './Sidebar';
import Footer from './Footer';
// 导入国际化Hook
import { useTranslation } from 'react-i18next';
// 定义Layout组件
function Layout() {
// 使用useTranslation Hook获取翻译函数
const { t } = useTranslation();
return (
<div className="layout">
{/* 头部导航 */}
<Header />
<div className="layout-body">
{/* 侧边栏 */}
<Sidebar />
{/* 主要内容区域 */}
<main className="layout-main" role="main">
{/* 使用Outlet渲染子路由 */}
<Outlet />
</main>
</div>
{/* 页脚 */}
<Footer />
</div>
);
}
// 导出Layout组件
export default Layout;
头部组件(详细注释版)
typescript
// src/components/layout/Header.tsx
// 导入React和useState
import React, { useState } from 'react';
// 导入路由组件
import { Link, useNavigate } from 'react-router-dom';
// 导入国际化Hook
import { useTranslation } from 'react-i18next';
// 导入组件
import LanguageSwitcher from '../LanguageSwitcher';
import UserMenu from '../UserMenu';
// 定义Header组件
function Header() {
// 使用useTranslation Hook获取翻译函数
const { t } = useTranslation();
// 使用useNavigate Hook获取导航函数
const navigate = useNavigate();
// 使用useState Hook管理移动端菜单状态
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
// 处理移动端菜单切换
const handleMobileMenuToggle = () => {
setMobileMenuOpen(prev => !prev);
};
return (
<header className="header" role="banner">
<div className="header-container">
{/* Logo */}
<div className="header-logo">
<Link to="/" aria-label={t('navigation.home')}>
<h1>My App</h1>
</Link>
</div>
{/* 桌面端导航 */}
<nav className="header-nav" aria-label="Main navigation">
<ul className="nav-list">
<li>
<Link to="/">{t('navigation.home')}</Link>
</li>
<li>
<Link to="/dashboard">{t('navigation.dashboard')}</Link>
</li>
<li>
<Link to="/tasks">{t('task.title')}</Link>
</li>
<li>
<Link to="/users">{t('user.profile')}</Link>
</li>
</ul>
</nav>
{/* 右侧操作区 */}
<div className="header-actions">
{/* 语言切换器 */}
<LanguageSwitcher />
{/* 用户菜单 */}
<UserMenu />
{/* 移动端菜单按钮 */}
<button
className="mobile-menu-button"
onClick={handleMobileMenuToggle}
aria-label="Toggle menu"
aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu"
>
<span className="hamburger-icon"></span>
</button>
</div>
</div>
{/* 移动端菜单 */}
{mobileMenuOpen && (
<nav
id="mobile-menu"
className="mobile-nav"
aria-label="Mobile navigation"
>
<ul className="mobile-nav-list">
<li>
<Link to="/" onClick={() => setMobileMenuOpen(false)}>
{t('navigation.home')}
</Link>
</li>
<li>
<Link to="/dashboard" onClick={() => setMobileMenuOpen(false)}>
{t('navigation.dashboard')}
</Link>
</li>
<li>
<Link to="/tasks" onClick={() => setMobileMenuOpen(false)}>
{t('task.title')}
</Link>
</li>
<li>
<Link to="/users" onClick={() => setMobileMenuOpen(false)}>
{t('user.profile')}
</Link>
</li>
</ul>
</nav>
)}
</header>
);
}
// 导出Header组件
export default Header;
任务管理页面(详细注释版)
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';
// 导入国际化Hook
import { useTranslation } from 'react-i18next';
// 导入组件
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 ErrorBoundary from '../components/ErrorBoundary';
// 导入类型
import { Task } from '../types/task';
// 定义Tasks页面组件
function Tasks() {
// 使用useTranslation Hook获取翻译函数
const { t } = useTranslation();
// 使用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');
const [searchQuery, setSearchQuery] = useState('');
// 使用useMemo Hook记忆化过滤和搜索后的任务
const filteredTasks = useMemo(() => {
let result = tasks;
// 应用过滤器
if (filter === 'active') {
result = result.filter(task => !task.completed);
} else if (filter === 'completed') {
result = result.filter(task => task.completed);
}
// 应用搜索
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
result = result.filter(task =>
task.title.toLowerCase().includes(query) ||
task.description?.toLowerCase().includes(query)
);
}
return result;
}, [tasks, filter, searchQuery]);
// 使用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(t('task.deleteTask'))) {
dispatch(deleteTask(id));
}
}, [dispatch, t]);
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 (
<ErrorBoundary>
<div className="tasks-page">
<div className="tasks-header">
<h1>{t('task.title')}</h1>
<Button onClick={() => setShowForm(true)}>
{t('task.addTask')}
</Button>
</div>
<div className="tasks-controls">
{/* 搜索框 */}
<input
type="text"
placeholder={t('common.search')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="search-input"
aria-label={t('common.search')}
/>
{/* 过滤器 */}
<div className="filter-buttons" role="group" aria-label={t('common.filter')}>
<Button
variant={filter === 'all' ? 'primary' : 'secondary'}
onClick={() => setFilter('all')}
aria-pressed={filter === 'all'}
>
{t('task.allTasks')}
</Button>
<Button
variant={filter === 'active' ? 'primary' : 'secondary'}
onClick={() => setFilter('active')}
aria-pressed={filter === 'active'}
>
{t('task.activeTasks')}
</Button>
<Button
variant={filter === 'completed' ? 'primary' : 'secondary'}
onClick={() => setFilter('completed')}
aria-pressed={filter === 'completed'}
>
{t('task.completedTasks')}
</Button>
</div>
</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>
</ErrorBoundary>
);
}
// 导出Tasks组件
export default Tasks;
样式文件(详细注释版)
css
/* src/styles/globals.css */
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 根元素样式 */
html {
font-size: 16px;
line-height: 1.5;
scroll-behavior: smooth;
}
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;
}
/* 布局样式 */
.layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.layout-body {
display: flex;
flex: 1;
min-height: 0;
}
.layout-main {
flex: 1;
padding: 2rem;
overflow-y: auto;
}
/* 头部样式 */
.header {
background-color: #333;
color: white;
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-logo h1 {
font-size: 1.5rem;
margin: 0;
}
.header-logo a {
color: white;
text-decoration: none;
}
.header-nav {
flex: 1;
display: flex;
justify-content: center;
}
.nav-list {
display: flex;
list-style: none;
gap: 2rem;
}
.nav-list a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.2s;
}
.nav-list a:hover,
.nav-list a:focus {
background-color: rgba(255, 255, 255, 0.1);
outline: 2px solid white;
outline-offset: 2px;
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.mobile-menu-button {
display: none;
background: none;
border: none;
color: white;
cursor: pointer;
padding: 0.5rem;
}
.hamburger-icon {
display: block;
width: 24px;
height: 2px;
background-color: white;
position: relative;
}
.hamburger-icon::before,
.hamburger-icon::after {
content: '';
position: absolute;
width: 24px;
height: 2px;
background-color: white;
left: 0;
}
.hamburger-icon::before {
top: -8px;
}
.hamburger-icon::after {
top: 8px;
}
.mobile-nav {
display: none;
background-color: #333;
padding: 1rem;
}
.mobile-nav-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 1rem;
}
.mobile-nav-list a {
color: white;
text-decoration: none;
padding: 0.5rem;
border-radius: 4px;
display: block;
}
.mobile-nav-list a:hover,
.mobile-nav-list a:focus {
background-color: rgba(255, 255, 255, 0.1);
outline: 2px solid white;
outline-offset: 2px;
}
/* 任务页面样式 */
.tasks-page {
max-width: 1200px;
margin: 0 auto;
}
.tasks-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.tasks-header h1 {
font-size: 2rem;
color: #333;
}
.tasks-controls {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 2rem;
padding: 1rem;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.search-input {
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
width: 100%;
}
.search-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.25);
}
.filter-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
/* 错误边界样式 */
.error-boundary {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
padding: 2rem;
}
.error-boundary-content {
text-align: center;
max-width: 600px;
}
.error-boundary-content h2 {
color: #dc3545;
margin-bottom: 1rem;
}
.error-details {
margin: 2rem 0;
text-align: left;
background-color: #f8f9fa;
padding: 1rem;
border-radius: 4px;
border: 1px solid #dee2e6;
}
.error-details summary {
cursor: pointer;
font-weight: bold;
margin-bottom: 0.5rem;
}
.error-details pre {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 0.875rem;
color: #666;
}
.reset-button {
padding: 0.75rem 1.5rem;
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
}
.reset-button:hover {
background-color: #5568d3;
}
.reset-button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.25);
}
/* 语言切换器样式 */
.language-switcher {
display: flex;
align-items: center;
}
.language-select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
color: #333;
cursor: pointer;
}
.language-select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.25);
}
/* 无障碍访问样式 */
.required-indicator {
color: #dc3545;
margin-left: 0.25rem;
}
.form-field {
margin-bottom: 1.5rem;
}
.form-field label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
}
.form-field label.required::after {
content: ' *';
color: #dc3545;
}
.form-field input,
.form-field textarea,
.form-field select {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-field input:focus,
.form-field textarea:focus,
.form-field select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.25);
}
.form-field input.error,
.form-field textarea.error,
.form-field select.error {
border-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.help-text {
color: #666;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header-container {
flex-wrap: wrap;
}
.header-nav {
display: none;
}
.mobile-menu-button {
display: block;
}
.mobile-nav {
display: block;
width: 100%;
}
.tasks-header {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.filter-buttons {
justify-content: center;
}
}
练习题目
基础练习
- 国际化练习
typescript
// 练习1:为现有应用添加国际化支持
// 实现:多语言切换、翻译资源管理
// 包含:i18next配置、翻译文件、语言切换器
// 练习2:实现日期和数字的本地化
// 实现:日期格式化、数字格式化、货币格式化
// 包含:本地化配置、格式化函数
- 无障碍访问练习
typescript
// 练习3:优化现有组件的无障碍性
// 实现:ARIA属性、键盘导航、屏幕阅读器支持
// 包含:语义化HTML、焦点管理、ARIA标签
// 练习4:实现完整的无障碍表单
// 实现:表单验证、错误提示、帮助文本
// 包含:WCAG标准、无障碍测试
进阶练习
- 错误处理练习
typescript
// 练习5:实现完整的错误处理系统
// 实现:错误边界、错误监控、错误报告
// 包含:错误分类、错误追踪、用户反馈
// 练习6:构建企业级应用
// 实现:微前端、国际化、无障碍、错误处理
// 包含:完整架构、最佳实践、性能优化