Follow these implementation conventions for all components:
- Typing: Use
React.FC<Props>. Name the interface <Name>Props and export it with export type. - Props: Use JSDoc for every prop (Chinese comments are the house style). Use destructuring for default values instead of
defaultProps. - Attributes: Extend native element attributes using
Omit to redefine specific fields (e.g., extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'>). - Class Names: Compose classes using the
classnames library or the [styles.a, cond && styles.b].filter(Boolean).join(' ') pattern. - Identity: Always set
displayName on the component. - State: Stateful components must support both controlled (
value) and uncontrolled (defaultValue) usage. - Naming:
- Files/Exports:
PascalCase (Button.tsx). - Styles:
lowercase-hyphen (button.module.less). - CSS Modules:
kebab-case (.btn-primary). - Type Aliases:
PascalCase (ButtonSize).
import React from 'react';
import styles from './component.module.less';
export type FooSize = 'small' | 'middle' | 'large';
export interface FooProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
/** 尺寸 */
size?: FooSize;
/** 禁用 */
disabled?: boolean;
children?: React.ReactNode;
}
export const Foo: React.FC<FooProps> = ({ size = 'middle', disabled = false, className, children, ...rest }) => {
const classNames = [styles.foo, styles[`foo-${size}`], disabled && styles['foo-disabled'], className]
.filter(Boolean)
.join(' ');
return (
<div className={classNames} aria-disabled={disabled || undefined} {...rest}>
{children}
</div>
);
};
Foo.displayName = 'Foo';