yggjs_rbutton React按钮组件v1.0.0 最佳实践指南

发布于:2025-08-30 ⋅ 阅读:(16) ⋅ 点赞:(0)

概述

本指南提供了使用 YggJS 多主题按钮系统的最佳实践和设计原则,帮助开发者构建高质量、高性能的用户界面。

目录

设计原则

1. 一致性优先

确保跨主题的 API 一致性是系统设计的核心原则。

✅ 推荐做法
// 使用统一的属性名和值
<TechButton variant="primary" size="medium" />
<MinimalButton variant="primary" size="medium" />

// 保持事件处理的一致性
const handleClick = useCallback((event) => {
  console.log('按钮被点击', event);
}, []);

<TechButton onClick={handleClick}>科技按钮</TechButton>
<MinimalButton onClick={handleClick}>极简按钮</MinimalButton>
❌ 避免做法
// 避免主题特定的属性名
<TechButton techVariant="cyber" techSize="big" />  // ❌
<MinimalButton minimalType="clean" minimalScale="large" />  // ❌

2. 性能导向

始终将性能作为第一考虑因素,特别是在样式计算和渲染方面。

✅ 推荐做法
// 使用 memo 优化不必要的重渲染
const OptimizedButton = memo(({ variant, children, ...props }) => {
  const styles = useMemo(() => 
    computeButtonStyles(variant, props), 
    [variant, props]
  );
  
  return (
    <TechButton css={styles} {...props}>
      {children}
    </TechButton>
  );
});

// 启用样式缓存
<StyleCacheProvider cacheSize={100}>
  <App />
</StyleCacheProvider>
❌ 避免做法
// 避免在渲染函数中进行复杂计算
function SlowButton({ variant }) {
  // ❌ 每次渲染都会重新计算
  const complexStyles = computeComplexStyles(variant);
  
  return <TechButton css={complexStyles}>按钮</TechButton>;
}

3. 类型安全

充分利用 TypeScript 的类型系统确保代码的健壮性。

✅ 推荐做法
// 定义严格的类型约束
interface ButtonGroupProps {
  buttons: Array<{
    id: string;
    label: string;
    variant: TechButtonProps['variant'];
    onClick: () => void;
  }>;
  theme: 'tech' | 'minimal';
}

const ButtonGroup: FC<ButtonGroupProps> = ({ buttons, theme }) => {
  const ButtonComponent = theme === 'tech' ? TechButton : MinimalButton;
  
  return (
    <div>
      {buttons.map(({ id, label, variant, onClick }) => (
        <ButtonComponent
          key={id}
          variant={variant}
          onClick={onClick}
        >
          {label}
        </ButtonComponent>
      ))}
    </div>
  );
};

代码组织

1. 模块化架构

项目结构建议
src/
├── components/             # 应用级组件
│   ├── forms/
│   ├── navigation/
│   └── feedback/
├── themes/                 # 主题配置
│   ├── tech/
│   ├── minimal/
│   └── custom/
├── hooks/                  # 自定义 Hook
│   ├── useThemeManager.ts
│   ├── useButtonState.ts
│   └── useAccessibility.ts
├── utils/                  # 工具函数
│   ├── theme-utils.ts
│   ├── style-utils.ts
│   └── performance.ts
├── types/                  # 类型定义
│   ├── theme.ts
│   ├── component.ts
│   └── global.ts
└── constants/              # 常量定义
    ├── themes.ts
    ├── breakpoints.ts
    └── animations.ts

2. 组件分层

// 1. 基础层:纯 UI 组件
export const BaseButton: FC<BaseButtonProps> = ({ children, ...props }) => {
  return <button {...props}>{children}</button>;
};

// 2. 主题层:添加主题样式
export const ThemedButton: FC<ThemedButtonProps> = ({ theme, ...props }) => {
  const styles = useThemeStyles(theme, props);
  return <BaseButton css={styles} {...props} />;
};

// 3. 业务层:添加业务逻辑
export const BusinessButton: FC<BusinessButtonProps> = ({ 
  analytics, 
  permission,
  ...props 
}) => {
  const handleClick = useAnalytics(analytics);
  const canClick = usePermission(permission);
  
  return (
    <ThemedButton
      {...props}
      onClick={canClick ? handleClick : undefined}
      disabled={!canClick}
    />
  );
};

3. Hook 组织

// 组合多个相关的 Hook
export const useButtonFeatures = (props: ButtonProps) => {
  const theme = useTheme();
  const accessibility = useAccessibility();
  const performance = usePerformanceMonitor();
  const analytics = useAnalytics();
  
  return {
    theme,
    accessibility,
    performance,
    analytics,
    // 组合后的便捷方法
    trackClick: analytics.trackButtonClick,
    announceToScreenReader: accessibility.announce,
  };
};

// 在组件中使用
const SmartButton: FC<ButtonProps> = (props) => {
  const { theme, trackClick, announceToScreenReader } = useButtonFeatures(props);
  
  const handleClick = useCallback((event) => {
    trackClick(props.variant);
    announceToScreenReader(`${props.children} 按钮已点击`);
    props.onClick?.(event);
  }, [props, trackClick, announceToScreenReader]);
  
  return (
    <TechButton {...props} onClick={handleClick} />
  );
};

性能优化

1. 样式优化

样式缓存策略
// 创建分层缓存系统
const createLayeredCache = () => {
  const baseCache = new Map<string, CSSObject>();      // 基础样式
  const variantCache = new Map<string, CSSObject>();   // 变体样式
  const stateCache = new Map<string, CSSObject>();     // 状态样式
  
  return {
    getBaseStyles: (key: string) => baseCache.get(key),
    setBaseStyles: (key: string, styles: CSSObject) => baseCache.set(key, styles),
    
    getVariantStyles: (key: string) => variantCache.get(key),
    setVariantStyles: (key: string, styles: CSSObject) => variantCache.set(key, styles),
    
    getStateStyles: (key: string) => stateCache.get(key),
    setStateStyles: (key: string, styles: CSSObject) => stateCache.set(key, styles),
  };
};
样式计算优化
// 使用 useMemo 缓存复杂计算
const useOptimizedStyles = (variant: string, size: string, disabled: boolean) => {
  return useMemo(() => {
    const baseStyles = getBaseButtonStyles();
    const variantStyles = getVariantStyles(variant);
    const sizeStyles = getSizeStyles(size);
    const stateStyles = disabled ? getDisabledStyles() : {};
    
    return {
      ...baseStyles,
      ...variantStyles,
      ...sizeStyles,
      ...stateStyles,
    };
  }, [variant, size, disabled]);
};

2. 渲染优化

虚拟化大量按钮
import { FixedSizeList as List } from 'react-window';

const VirtualizedButtonList: FC<{ buttons: ButtonData[] }> = ({ buttons }) => {
  const Row = useCallback(({ index, style }) => (
    <div style={style}>
      <TechButton variant={buttons[index].variant}>
        {buttons[index].label}
      </TechButton>
    </div>
  ), [buttons]);

  return (
    <List
      height={400}
      itemCount={buttons.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </List>
  );
};
懒加载主题资源
// 动态加载主题
const useLazyTheme = (themeName: string) => {
  const [theme, setTheme] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;

    const loadTheme = async () => {
      setLoading(true);
      
      try {
        const themeModule = await import(`../themes/${themeName}`);
        if (!cancelled) {
          setTheme(themeModule.default);
        }
      } catch (error) {
        console.error('主题加载失败:', error);
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    loadTheme();

    return () => {
      cancelled = true;
    };
  }, [themeName]);

  return { theme, loading };
};

3. Bundle 优化

// webpack 配置优化
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        // 将主题代码分离
        themes: {
          test: /[\\/]themes[\\/]/,
          name: 'themes',
          chunks: 'all',
          priority: 10,
        },
        // 将样式工具分离
        styleUtils: {
          test: /[\\/]style-utils[\\/]/,
          name: 'style-utils',
          chunks: 'all',
          priority: 8,
        },
      },
    },
  },
};

可访问性

1. ARIA 支持

const AccessibleButton: FC<AccessibleButtonProps> = ({
  children,
  describedBy,
  pressed,
  expanded,
  ...props
}) => {
  const buttonId = useId();
  const ariaProps = useMemo(() => ({
    id: buttonId,
    'aria-describedby': describedBy,
    'aria-pressed': pressed,
    'aria-expanded': expanded,
    role: 'button',
  }), [buttonId, describedBy, pressed, expanded]);

  return (
    <TechButton {...props} {...ariaProps}>
      {children}
    </TechButton>
  );
};

2. 键盘导航

const useKeyboardNavigation = () => {
  const handleKeyDown = useCallback((event: KeyboardEvent) => {
    switch (event.key) {
      case 'Enter':
      case ' ':
        event.preventDefault();
        (event.target as HTMLElement).click();
        break;
      case 'Tab':
        // 处理焦点管理
        manageFocus(event);
        break;
    }
  }, []);

  return { handleKeyDown };
};

3. 屏幕阅读器优化

const useScreenReaderOptimization = () => {
  const announceToScreenReader = useCallback((message: string) => {
    const announcement = document.createElement('div');
    announcement.setAttribute('aria-live', 'polite');
    announcement.setAttribute('aria-atomic', 'true');
    announcement.textContent = message;
    announcement.style.position = 'absolute';
    announcement.style.left = '-10000px';
    
    document.body.appendChild(announcement);
    
    setTimeout(() => {
      document.body.removeChild(announcement);
    }, 1000);
  }, []);

  return { announceToScreenReader };
};

测试策略

1. 单元测试

// 组件行为测试
describe('TechButton', () => {
  it('应该正确处理点击事件', () => {
    const handleClick = jest.fn();
    render(<TechButton onClick={handleClick}>点击我</TechButton>);
    
    fireEvent.click(screen.getByRole('button'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('应该根据 variant 应用正确的样式', () => {
    const { container } = render(<TechButton variant="primary">按钮</TechButton>);
    const button = container.firstChild;
    
    expect(button).toHaveClass('tech-button-primary');
  });
});

2. 视觉回归测试

import { render } from '@testing-library/react';
import { toMatchSnapshot } from 'jest-image-snapshot';

expect.extend({ toMatchSnapshot });

describe('按钮视觉测试', () => {
  it('科技风按钮快照测试', () => {
    const component = render(
      <TechButton variant="primary" size="large">
        科技按钮
      </TechButton>
    );
    
    expect(component.container.firstChild).toMatchSnapshot();
  });
});

3. 可访问性测试

import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

describe('可访问性测试', () => {
  it('按钮应该无可访问性违规', async () => {
    const { container } = render(
      <TechButton variant="primary">可访问按钮</TechButton>
    );
    
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

4. 性能测试

describe('性能测试', () => {
  it('大量按钮渲染应该在性能阈值内', () => {
    const startTime = performance.now();
    
    const buttons = Array.from({ length: 1000 }, (_, i) => (
      <TechButton key={i} variant="primary">按钮 {i}</TechButton>
    ));
    
    render(<div>{buttons}</div>);
    
    const endTime = performance.now();
    const renderTime = endTime - startTime;
    
    expect(renderTime).toBeLessThan(100); // 100ms 阈值
  });
});

主题设计

1. 科技风主题设计原则

const TECH_THEME_PRINCIPLES = {
  // 色彩:使用科技感强的蓝色和紫色系
  colors: {
    primary: {
      50: '#eff6ff',
      500: '#3b82f6',
      900: '#1e3a8a',
    },
    accent: {
      50: '#faf5ff',
      500: '#8b5cf6',
      900: '#581c87',
    },
  },
  
  // 效果:强调发光和渐变
  effects: {
    glow: '0 0 20px rgba(59, 130, 246, 0.5)',
    gradient: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
  },
  
  // 动画:流畅的科技感过渡
  animations: {
    duration: '300ms',
    easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
  },
};

2. 极简主题设计原则

const MINIMAL_THEME_PRINCIPLES = {
  // 色彩:使用温和的灰度系统
  colors: {
    gray: {
      50: '#f9fafb',
      500: '#6b7280',
      900: '#111827',
    },
  },
  
  // 效果:微妙的阴影和边框
  effects: {
    shadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
    border: '1px solid rgba(0, 0, 0, 0.1)',
  },
  
  // 动画:温和的过渡效果
  animations: {
    duration: '200ms',
    easing: 'ease-out',
  },
};

3. 主题一致性验证

const validateThemeConsistency = (theme: ThemeDefinition) => {
  const errors: string[] = [];
  
  // 检查必需的颜色定义
  const requiredColors = ['primary', 'secondary', 'danger', 'success'];
  for (const color of requiredColors) {
    if (!theme.colors[color]) {
      errors.push(`缺少必需颜色: ${color}`);
    }
  }
  
  // 检查字体系统
  if (!theme.typography?.fontFamily) {
    errors.push('缺少字体系统定义');
  }
  
  // 检查间距系统
  if (!theme.spacing) {
    errors.push('缺少间距系统定义');
  }
  
  return {
    valid: errors.length === 0,
    errors,
  };
};

国际化支持

1. 文本国际化

// 多语言按钮文本
const useButtonI18n = () => {
  const { t } = useTranslation();
  
  return {
    loading: t('button.loading', '加载中...'),
    disabled: t('button.disabled', '按钮已禁用'),
    clickToAction: t('button.clickToAction', '点击执行操作'),
  };
};

// 在组件中使用
const I18nButton: FC<ButtonProps> = ({ loading, children, ...props }) => {
  const i18n = useButtonI18n();
  
  return (
    <TechButton {...props}>
      {loading ? i18n.loading : children}
    </TechButton>
  );
};

2. RTL 支持

const useRTLSupport = () => {
  const { direction } = useLocale();
  
  const rtlStyles = useMemo(() => {
    if (direction === 'rtl') {
      return {
        transform: 'scaleX(-1)',
        '& > *': {
          transform: 'scaleX(-1)',
        },
      };
    }
    return {};
  }, [direction]);
  
  return rtlStyles;
};

3. 地区特定样式

const useLocaleStyles = () => {
  const { locale } = useLocale();
  
  return useMemo(() => {
    const baseStyles = getBaseStyles();
    
    switch (locale) {
      case 'ja-JP':
        return {
          ...baseStyles,
          fontFamily: 'Noto Sans JP, sans-serif',
          letterSpacing: '0.1em',
        };
      case 'ar-SA':
        return {
          ...baseStyles,
          fontFamily: 'Noto Sans Arabic, sans-serif',
          direction: 'rtl',
        };
      default:
        return baseStyles;
    }
  }, [locale]);
};

总结

遵循这些最佳实践可以帮助你:

  1. 构建高质量的用户界面:通过一致的设计原则和代码组织
  2. 优化性能表现:通过样式缓存、懒加载等技术
  3. 提升可访问性:支持键盘导航、屏幕阅读器等
  4. 保证代码质量:通过完善的测试策略
  5. 支持国际化:适配多语言和多地区需求

记住,最佳实践是随着项目需求和技术发展而演进的,请根据实际情况调整和优化这些建议。


版本: 1.0.0
最后更新: 2025年
维护者: 源滚滚AI编程