组件设计原则
组件开发规范和设计原则是构建高质量前端应用的基础。良好的规范能够提升代码的可维护性和可读性,而合理的设计原则则能确保组件的复用性和扩展性。从命名约定到状态管理,从样式隔离到API设计,每个环节都需要严格遵循最佳实践。
命名规范
组件命名应采用大驼峰式(PascalCase),与文件名保持一致。对于基础组件,应添加统一前缀以增强辨识度:
// 正确示例
import Button from './BaseButton';
import IconClose from './IconClose';
// 错误示例
import btn from './button';
CSS类名使用BEM命名法,通过双下划线表示元素,双中划线表示修饰符:
/* 正确示例 */
.menu__item--active {
color: #1890ff;
}
/* 错误示例 */
.menu .active-item {
color: red;
}
单一职责原则
每个组件应只解决一个特定问题。当发现组件承担过多功能时,应该进行拆分:
// 过度复杂的组件
function UserProfile({ user, onEdit, onDelete }) {
return (
<div>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
<button onClick={onEdit}>Edit</button>
<button onClick={onDelete}>Delete</button>
</div>
);
}
// 拆分为专注单一功能的组件
function UserAvatar({ src, alt }) {
return <Avatar src={src} alt={alt} />;
}
function UserActions({ onEdit, onDelete }) {
return (
<>
<button onClick={onEdit}>Edit</button>
<button onClick={onDelete}>Delete</button>
</>
);
}
受控与非受控组件
表单组件需要明确区分受控和非受控模式。受控组件通过props完全控制状态:
// 受控输入框
function ControlledInput({ value, onChange }) {
return <input value={value} onChange={onChange} />;
}
非受控组件通过ref获取DOM元素值,适合表单数据只需最终提交的场景:
// 非受控输入框
function UncontrolledInput({ defaultValue }) {
const inputRef = useRef(null);
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<>
<input ref={inputRef} defaultValue={defaultValue} />
<button onClick={handleSubmit}>Submit</button>
</>
);
}
样式隔离方案
避免全局样式污染,推荐采用CSS Modules或styled-components方案:
// CSS Modules示例
import styles from './Button.module.css';
function Button() {
return <button className={styles.primary}>Click</button>;
}
// styled-components示例
const StyledButton = styled.button`
background: ${props => props.primary ? '#1890ff' : '#fff'};
padding: 8px 16px;
`;
组件API设计
组件props设计应遵循最小暴露原则,使用PropTypes或TypeScript进行类型约束:
interface ModalProps {
visible: boolean;
title?: string;
onClose: () => void;
width?: number | string;
}
function Modal({ visible, title, onClose, width = '50%' }: ModalProps) {
return (
<div className="modal" style={{ width }}>
{title && <h2>{title}</h2>}
<button onClick={onClose}>×</button>
</div>
);
}
状态管理策略
根据组件层级选择适当的状态管理方式。对于局部状态使用useState,跨组件状态使用Context:
// 创建Context
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
<button onClick={() => setTheme('light')}>Toggle Theme</button>
</ThemeContext.Provider>
);
}
// 消费Context
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>Current Theme: {theme}</div>;
}
性能优化手段
通过React.memo避免不必要的渲染,对大型列表使用虚拟滚动:
// 使用memo优化
const MemoComponent = memo(function MyComponent({ data }) {
return <div>{data}</div>;
});
// 虚拟列表示例
import { FixedSizeList as List } from 'react-window';
function VirtualList({ items }) {
return (
<List
height={400}
itemCount={items.length}
itemSize={50}
width={300}
>
{({ index, style }) => (
<div style={style}>Row {items[index]}</div>
)}
</List>
);
}
错误边界处理
使用错误边界组件捕获子组件树中的JavaScript错误:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info);
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
// 使用方式
<ErrorBoundary>
<UnstableComponent />
</ErrorBoundary>
可访问性要求
确保组件符合WAI-ARIA标准,为屏幕阅读器提供必要信息:
function AccessibleButton({ children }) {
return (
<button
aria-label="关闭对话框"
aria-pressed={false}
onClick={handleClick}
>
{children}
</button>
);
}
文档规范
每个组件应配套README文档,包含以下内容:
- 基本用法示例
- Props详细说明
- 事件处理说明
- 样式覆盖指南
- 注意事项和边界条件
## Button 组件
### 基础用法
```jsx
<Button type="primary">确认</Button>
Props
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
type | 'primary'|'default' | 'default' | 按钮类型 |
disabled | boolean | false | 禁用状态 |
## 测试策略
组件应包含单元测试和交互测试,验证核心功能:
```jsx
// Jest测试示例
test('Button renders with correct text', () => {
const { getByText } = render(<Button>Submit</Button>);
expect(getByText('Submit')).toBeInTheDocument();
});
// 交互测试示例
test('Button triggers onClick', () => {
const handleClick = jest.fn();
const { getByRole } = render(<Button onClick={handleClick} />);
fireEvent.click(getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
版本兼容性
组件库需要明确支持的浏览器范围和polyfill策略:
// package.json中的browserslist配置
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version"
]
}
}
国际化支持
组件文本内容应支持多语言切换,避免硬编码:
// 使用i18n方案
function Greeting() {
const { t } = useTranslation();
return <h1>{t('welcome_message')}</h1>;
}
// 日期本地化处理
new Date().toLocaleDateString(navigator.language);
主题定制方案
通过CSS变量或主题Provider实现动态换肤:
// CSS变量方案
:root {
--primary-color: #1890ff;
}
.button {
background: var(--primary-color);
}
// JS主题方案
const theme = {
colors: {
primary: '#1890ff'
}
};
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
组件组合模式
通过children prop或render props实现灵活的组合:
// children组合
function Card({ children }) {
return <div className="card">{children}</div>;
}
<Card>
<CardHeader />
<CardBody />
</Card>
// render props模式
function Toggle({ render }) {
const [on, setOn] = useState(false);
return render({ on, toggle: () => setOn(!on) });
}
<Toggle render={({ on, toggle }) => (
<button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>
)} />
类型定义文件
为JavaScript组件提供TypeScript类型声明:
// types/component.d.ts
declare module 'ui-library' {
interface ButtonProps {
size?: 'small' | 'medium' | 'large';
variant?: 'primary' | 'secondary';
}
export const Button: React.FC<ButtonProps>;
}
构建输出规范
组件库应输出多种模块格式:
// rollup.config.js
export default {
input: 'src/index.js',
output: [
{ file: 'dist/index.esm.js', format: 'es' },
{ file: 'dist/index.cjs.js', format: 'cjs' },
{ file: 'dist/index.umd.js', format: 'umd', name: 'ComponentLib' }
]
};
发布流程控制
采用semver版本规范,遵循commit message约定:
# 标准版本发布命令
npm version patch -m "fix: 修复按钮点击区域问题"
npm publish
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn