React로 개발할 때 하나의 컴포넌트가 너무 많은 역할을 하게 되면 가독성이 떨어지고 유지보수가 어려워짐. 예를 들어, UI 렌더링, 데이터 관리, API 호출 등 다양한 로직을 한 컴포넌트에서 처리하면 코드가 복잡해지고 수정이 어려워질 수 있음. 관심사 분리(Separation of Concerns, SoC)를 적용하여 컴포넌트를 역할별로 나누면 더 깨끗하고 유지보수가 용이한 코드를 작성할 수 있음.
아래 코드에서 UserProfile 컴포넌트는 데이터 로딩, 상태 관리, UI 렌더링을 모두 한 컴포넌트에서 처리하고 있음. 이로 인해 컴포넌트가 길어지고, 수정 시 많은 부분에 영향을 줄 가능성이 있음.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
);
}
export default UserProfile;
이 컴포넌트는 다음과 같은 여러 책임을 가지고 있음
관심사 분리를 위해, 각 기능을 별도의 커스텀 훅과 컴포넌트로 나누어 재구성할 수 있음.
import { useState, useEffect } from 'react';
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
return { user, loading, error };
}
export default useUser;
위의 useUser 훅은 API 호출과 상태 관리를 담당하여, 컴포넌트는 이 훅을 호출하여 필요한 데이터를 가져올 수 있음. 이렇게 하면 UserProfile 컴포넌트에서 API 로직을 분리할 수 있음.
이제 UserProfile 컴포넌트는 UI 렌더링에만 집중할 수 있음. 데이터를 가져오는 로직은 useUser 훅을 통해 처리되고, UserProfile은 상태에 따라 UI를 보여주기만 함.
import React from 'react';
import useUser from './useUser';
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
);
}
export default UserProfile;
이제 UserProfile 컴포넌트는 데이터를 로딩하는 로직을 알 필요가 없으며, UI와 상태별 렌더링에만 집중할 수 있음.