概述
本指南提供了使用 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.0.0
最后更新: 2025年
维护者: 源滚滚AI编程