忙しい人向けまとめ
React やデザインシステムに触れたばかりだと意識しづらいポイント
-
className
を自由に渡せる設計にすると、スタイルの統一性が崩れる -
子コンポーネントを適切に分割しないと、管理が難しくなる
-
呼び出し側で必要な
props
を定義してから実装しないと使いづらい
デザインシステムの意図を考慮しながらコンポーネントを設計することが大切
Before: 課題のあるコンポーネント
コード例はNext.js, スタイリングはvanilla-extractです
interface ButtonProps {
label?: string;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
disabled?: boolean;
icon?: React.ReactNode;
iconPosition?: 'before' | 'after';
isLoading?: boolean; // isLoadingがデザインシステムにない
className?: string;
style?: React.CSSProperties;
}
const Button: React.FC<ButtonProps> = ({
label,
icon,
iconPosition = 'before',
onClick,
disabled,
isLoading,
className,
style,
}) => {
return (
<button onClick={onClick} className={className} style={style} disabled={disabled}>
{isLoading ? 'Loading..' : (
<>
{icon && iconPosition === 'before' && <span>{icon}</span>}
{label}
{icon && iconPosition === 'after' && <span>{icon}</span>}
</>
)}
</button>
);
};
export default Button;
課題点
-
className
を自由に渡せるため、デザインの統一性が崩れる -
props
の整理が甘く、呼び出し側で余計な考慮が必要になる -
label
やicon
の使い方が統一されておらず、意図が伝わりにくい
<Button
label="送信" // ラベルをchildrenにするかpropsにするのかは制限による
onClick={() => alert('送信しました')}
icon={<SendIcon />}
iconPosition="after"
className={primaryButtonM} // ここの自由度が高すぎるのと直感的でない
/>
After: シンプルで使いやすい設計
デザインシステムごとに適切なコンポーネントを分け、props
の自由度を抑えることで統一感を確保すると使いやすい。
type Props = {
children: React.ReactNode;
size?: 's' | 'm' | 'l';
variant?: 'green' | 'blue' | 'lightBlue' | 'purple' | 'gold' | 'red';
disabled: boolean;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
export const PrimaryButton: React.FC<Props> = ({
children,
size = 'm',
variant = 'green',
disabled = false,
onClick,
}) => {
return (
<button
className={`${buttonBase} ${sizes[size]} ${
disabled ? primaryDisabled[variant] : primaryVariants[variant]
}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
デザインシステムごとにスタイルを統一
-
className
を自由に渡せないので、見た目のバラつきを防ぐ -
children
を必須にして柔軟なラベル表示を可能に
<PrimaryButton size="s" variant="purple" onClick={() => alert('送信しました')}>送信</PrimaryButton>
別例(アイコンとラベルはセットの場合)
export const FormButton: React.FC<Props> = ({
children,
icon,
onClick,
disabled,
...props
}) => {
return (
<button className={formButton} disabled={disabled} type="button" onClick={onClick} {...props}>
<span className={iconStyle}>{icon}</span>
{children}
</button>
);
};
-
アイコンとラベルはセットで必須
-
className
を固定し、デザインの統一感を維持 -
必要最低限の
props
だけを渡せばOK
使い方
<FormButton icon={<RefreshCw />} onClick={handleClick}>
更新
</FormButton>
もしアイコンとテキストの組み合わせを完全に固定したくなったら ...
<FormButton variant="refresh" onClick={handleClick}>
更新
</FormButton>
のように variant を受け取る形にするというように修正する
まとめ
-
呼び出し側で余計な考慮が必要となる自由なpropsに注意
-
デザインシステムに沿って設計することで統一感と管理しやすさが向上
-
コンポーネントごとにルールを決めると、実装のブレが少なくなる
誰でも迷わず使えるコンポーネントを目指すとこんな感じになるのかなと思って書きましたが間違っているところもあると思います
デザシス x 実装についてリファレンスにできるしょできそうな関連書籍などあれば紹介いただけると嬉しいです