Bad
// コンポーネントが表示するだけでなく、データをフェッチする処理を持っている
function BadComponent() {
const [prices, setPrices] = useState([]);
const fetchPrices = () => {
fetch(somethingUrl)
.then((res) => res.json())
.then((data) => {
setPrices(data);
});
}
useEffect(() => {
fetchPrices();
}, []);
// 金額一覧を表示
return {...};
}
Good
// コンポーネントは表示のみを行っている
function GoodComponent {
const prices = useFetchPrices();
// 金額一覧を表示
return {...};
}
// フェッチ処理をカスタムフックスに閉じる
function useFetchPrices() {
const [prices, setPrices] = useState([]);
const fetchPrices = () => {
fetch(somethingUrl)
.then((res) => res.json())
.then((data) => {
setPrices(data);
});
}
useEffect(() => {
fetchPrices();
}, []);
return prices;
}
Bad
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string
role: 'main' | 'back' // ここに追加があった場合、変更箇所が増える。可読性も悪い。
}
function Button({ text, role }) {
return (
<button {...props}>
{text}
{/* roleによって表示を切り替える処理 */}
{role === 'main' && <MainButtonIcon />}
{role === 'back' && <BackButtonIcon />}
</button>
)
}
Good
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
text: string
icon: React.ReactNode // 表示するIconをpropsで渡す
}
function Button({ text, icon, ...props }) {
return (
<button {...props}>
{text}
{icon}
</button>
)
}
// 使用する側
function Buttons() {
return (
<>
<Button text="メインボタン" icon={<MainButtonIcon />} />
<Button text="戻る" icon={<BackButtonIcon />} />
</>
)
}
Bad
// SearchInputコンポーネントがinputタグを継承していない
interface SearchInputProps {
placeholder: string
value: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}
function SearchInput({ placeholder, value, onChange }: SearchInputProps) {
return (
<input
type="text"
placeholder={placeholder}
value={value}
onChange={onChange}
/>
)
}
Good
// SearchInputコンポーネントがinputタグを継承している
interface SearchInputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
function SearchInput({ ...props }: SearchInputProps) {
return <input {...props} />
}
Bad
// Thumbnailコンポーネントがuserオブジェクトを受け取って不要なデータも渡してしまっている。
interface ThumbnailProps {
user: {
name: string
age: number
thumbnail: string
}
}
function Thumbnail({ user }: ThumbnailProps) {
const { thumbnail } = user
return <img src={user.thumbnail} />
}
Good
// Thumbnailコンポーネントが必要なデータのみを受け取るようにする
interface ThumbnailProps {
thumbnailUrl: string
}
function Thumbnail({ thumbnailUrl }: ThumbnailProps) {
return <img src={thumbnailUrl} />
}
Bad
// FormコンポーネントがSubmit関数を内部で持っている
function Form() {
const [user, setUser] = useState({ name: '', age: 0 });
const handleSubmit = (e: React.FormEvent) => {
// formの送信処理
await axios.post('/api/users', { name, age });
}
// formの表示
return {...};
}
Good
// FormコンポーネントがSubmit関数をpropsとして受け取る
interface FormProps {
onSubmit: (user: { name: string; age: number }) => void;
}
function Form({ onSubmit }: FormProps) {
const [user, setUser] = useState({ name: '', age: 0 });
const handleSubmit = (e: React.FormEvent) => {
onSubmit(user);
}
// formの表示
return {...};
}
// 使用する側
function FormPage() {
const handleSubmit = (user: { name: string; age: number }) => {
// formの送信処理
await axios.post('/api/users', { name, age });
}
return <Form onSubmit={handleSubmit} />;
}
This is the Only Right Way to Write React clean-code - SOLID
<!-- SOLIDで有名な記事 -->