React 18.x 学习计划 - 第十二天:企业级实践与进阶主题

学习目标

  • 理解微前端架构
  • 掌握国际化实现
  • 学会无障碍访问开发
  • 理解错误处理和监控
  • 构建完整的企业级应用

学习时间安排

总时长: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;
  }
}

练习题目

基础练习

  1. 国际化练习
typescript 复制代码
// 练习1:为现有应用添加国际化支持
// 实现:多语言切换、翻译资源管理
// 包含:i18next配置、翻译文件、语言切换器

// 练习2:实现日期和数字的本地化
// 实现:日期格式化、数字格式化、货币格式化
// 包含:本地化配置、格式化函数
  1. 无障碍访问练习
typescript 复制代码
// 练习3:优化现有组件的无障碍性
// 实现:ARIA属性、键盘导航、屏幕阅读器支持
// 包含:语义化HTML、焦点管理、ARIA标签

// 练习4:实现完整的无障碍表单
// 实现:表单验证、错误提示、帮助文本
// 包含:WCAG标准、无障碍测试

进阶练习

  1. 错误处理练习
typescript 复制代码
// 练习5:实现完整的错误处理系统
// 实现:错误边界、错误监控、错误报告
// 包含:错误分类、错误追踪、用户反馈

// 练习6:构建企业级应用
// 实现:微前端、国际化、无障碍、错误处理
// 包含:完整架构、最佳实践、性能优化
相关推荐
lili-felicity2 小时前
React Native 鸿蒙跨平台开发:useColorScheme 自定义鸿蒙端深色模式的主题配色
react native·react.js·harmonyos
代码游侠2 小时前
学习笔记——嵌入式与51单片机学习
单片机·嵌入式硬件·学习·51单片机
LateFrames2 小时前
极限:从基础数学,漫游到AI算力资源的分配
学习
qq_529599382 小时前
reactnative获取经纬度 获取此地信息 @react-native-community/geolocation
javascript·react native·react.js
工藤学编程2 小时前
零基础学AI大模型之旅游规划智能体之react_agent实战
人工智能·react.js·旅游
前端大波2 小时前
使用webpack-bundle-analyzer 对 react 老项目进行打包优化
前端·react.js·webpack·性能优化
代码游侠2 小时前
学习笔记——嵌入式系统与51单片机核心
笔记·单片机·嵌入式硬件·学习·51单片机
好奇龙猫2 小时前
【人工智能学习-AI入试相关题目练习-第一次】
人工智能·学习
前端不太难2 小时前
Flutter 状态复杂度,如何在架构层提前“刹车”
flutter·架构·状态模式