많은 프론트엔드 엔지니어들이 React 컴포넌트가 너무 거대해져서 프론트엔드 코드를 탐색할 때 계속 생각을 잃는 당황스러운 순간을 경험했을 것입니다. 가독성을 회복하기 위해 가장 흔한 방법 중 하나는 거대한 React 컴포넌트를 더 작은 조각으로 나누는 것입니다. 전통적으로는 JSX 요소를 잘라서 이를 수행했습니다. 이 방법은 데이터 표시만 담당하는 React 컴포넌트의 가독성을 효과적으로 향상시킬 수 있습니다. 하지만 데이터 표시 외에도 일부 React 컴포넌트는 백엔드에서 데이터를 가져오는 역할도 합니다. 이러한 React 컴포넌트를 가독성 있게 만드는 더 효과적인 방법은 컨테이너(Container)와 뷰(View)로 분리하는 것입니다. 이 글에서는 관심사 분리 원칙에 따라 React 컴포넌트를 컨테이너(Container)와 뷰(View)로 나누는 방법을 보여드리겠습니다.
컨테이너와 뷰란 무엇인가?
먼저, 컨테이너와 뷰가 각각 무엇을 담당하는지 명확히 하겠습니다. '컨테이너' 컴포넌트는 데이터를 가져오고 상태를 유지하는 역할을 합니다. 데이터 제시에는 전혀 신경 쓰지 않습니다. 반면, '뷰' 컴포넌트의 유일한 책임은 데이터를 표시하는 것입니다. 백엔드에서 데이터를 어떻게 가져오는지에 대해 신경 쓸 필요가 없습니다.
아마 눈치채셨겠지만, '컨테이너'와 '보기' 구성 요소 사이에 뚜렷한 고려사항이 분리되어 있습니다. 이러한 접근법은 전체 코드 가독성을 향상시키는 데 도움을 줍니다. '컨테이너' 구성 요소를 살펴볼 때는 데이터를 검색하고 업데이트하는 방법에 집중할 수 있습니다. '보기' 구성 요소를 다룰 때는 사용자에게 데이터를 제공하는 방식에 집중할 수 있습니다.
컴포넌트를 '컨테이너'와 '뷰'로 어떻게 나누나요?
이 섹션에서는 React 컴포넌트를 '컨테이너'와 '뷰' 컴포넌트로 나누는 간단한 예시를 시연하겠습니다. 예를 들어, 페이지 레벨 컴포넌트가 있다고 합시다. 이 컴포넌트는 백엔드에서 사용자 정보를 가져오고, 사용자 정보를 표시하며, 사용자가 닉네임을 변경할 수 있게 해줍니다.
import { useEffect, useState } from 'react';
'./UserPage.module.css'에서 스타일을 가져오는 것;
import { useEffect, useState } from 'react';
'./UserPage.module.css'에서 스타일을 가져오는 것;
유형 사용자 = {
ID: 번호;
이름: 스트링;
성: 스트링;
이메일: 스트링;
avatarUrl: string;
별명: 스트링;
};
type UserPageProps = {
사용자 ID: 번호;
};
export 함수 UserPage({ userId }: UserPageProps) {
const [user, setUser] = useState(null);
const [newNickname, setNewNickname] = useState('');
빈 의존 배열을 'useEffect' 훅에 전달하면, 컴포넌트가 생성될 때 전달한 함수는 한 번만 실행됩니다.
useEffect(() => {
fetch('/api/v1/users/${userId}', {
메서드: 'GET',
헤더: {
'Content-Type': 'application/json',
},
})
.then((res) => {
리턴 res.json();
})
.then((user: User) => {
setUser(user);
});
}, []);
const handleNicknameSubmit = () => {
setUser(null);
fetch('/api/v1/users/${userId}/nickname', {
방법: 'POST',
헤더: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
별명: newNickname,
}),
})
.then((res) => {
리턴 res.json();
})
.then((user: User) => {
setUser(user);
});
};
사용자 복귀 ? (
<>
이름: {user.firstName}
성: {user.lastName}
별명:
{
setNewNickname(e.target.value);
}}
/>
제출하세요
) : (
로딩 중...
);
}
이 페이지 수준 컴포넌트는 먼저 '/api/v1/users/${userId}'에 'GET' 요청을 보내 마운트 중인 현재 사용자의 정보를 가져옵니다. 그 후 이 컴포넌트는 정보를 받으면 표시합니다. 이 컴포넌트는 현재 사용자가 별명을 변경할 수 있는 입력도 가지고 있습니다. 사용자가 '제출' 버튼을 클릭하면 현재 사용자의 닉네임을 업데이트하기 위해 '/api/v1/users/${userId}/nickname'에 'POST' 요청을 발동합니다.
제 경력 동안 이와 같은 페이지 단위 구성 요소들을 많이 보았고, 일부는 더 복잡한 것들도 있습니다. 이들은 모든 것을 포괄하며, 한 곳에서 모든 것을 찾을 수 있습니다. 하지만 한 곳에 너무 많은 일이 일어나서 읽을 때 길을 잃기 쉽습니다.
다음으로, 이 페이지 수준의 컴포넌트를 '컨테이너'와 '보기' 컴포넌트로 나누어 더 읽기 쉽게 만들어봅시다.
import { useEffect, useState } from 'react';
'./UserPage.module.css'에서 스타일을 가져오는 것;
import { useEffect, useState } from 'react';
'./UserPage.module.css'에서 스타일을 가져오는 것;
유형 사용자 = {
ID: 번호;
이름: 스트링;
성: 스트링;
이메일: 스트링;
avatarUrl: string;
별명: 스트링;
};
type UserPageViewProps = {
사용자: 사용자;
새로운 별명: 끈;
setNewNickname: (newNickname: string) => void;
handleNicknameSubmit: () => void;
};
function UserPageView({
사용자,
newNickname,
setNewNickname,
handleNicknameSubmit,
}: UserPageViewProps) {
반환 (
<>
이름: {user.firstName}
성: {user.lastName}
별명:
{
setNewNickname(e.target.value);
}}
/>
제출하세요
);
}
type UserPageContainerProps = {
사용자 ID: 번호;
};
export 함수 UserPageContainer({ userId }: UserPageContainerProps) {
const [user, setUser] = useState(null);
const [newNickname, setNewNickname] = useState('');
useEffect(() => {
fetch('/api/v1/users/${userId}', {
메서드: 'GET',
헤더: {
'Content-Type': 'application/json',
},
})
.then((res) => {
리턴 res.json();
})
.then((user: User) => {
setUser(user);
});
}, []);
const handleNicknameSubmit = () => {
setUser(null);
fetch('/api/v1/users/${userId}/nickname', {
방법: 'POST',
헤더: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
별명: newNickname,
}),
})
.then((res) => {
리턴 res.json();
})
.then((user: User) => {
setUser(user);
});
};
만약 (!user) {
반환 로딩...;
}
반환 (
);
}
위 코드에서 볼 수 있듯이, 사용자 정보 표시용 코드를 추출하여 'UserPageView'라는 새로운 'View' 컴포넌트로 만들었습니다. 사용자 정보를 가져오고 사용자 닉네임을 변경하는 API 호출 로직은 원래 페이지 수준 컴포넌트에 그대로 두고 'UserPageContainer'로 이름을 바꿨습니다. 'UserPageView' 컴포넌트 작업에서는 어떤 데이터를 표시하는지, 요소의 스타일링에 집중할 수 있습니다. 'UserPageContainer'를 조정할 때는 상태 유지보수나 사용자 정보 가져오기 및 업데이트 등 관련 없는 일에 신경 쓸 필요가 없습니다. 전반적으로 이 페이지의 프론트엔드 코드를 읽고 리팩토링하는 것이 더 쉬워집니다.
이 글에서는 컨테이너와 뷰를 정의하는 것부터 시작했습니다. 그 다음, React 컴포넌트를 컨테이너와 뷰로 나누면 가독성이 향상되는 이유를 설명했습니다. 마지막으로, 구체적인 예시와 함께 React 컴포넌트를 컨테이너와 뷰로 분리하는 방법을 시연했습니다. 이 기법으로 React 컴포넌트를 리팩토링하는 데 시간이 많이 걸릴 수 있지만, 장기적으로 당신과 동료들의 업무 효율성을 높일 수 있습니다. 시간이 될 때 이 리팩토링 기법을 꼭 시도해 보세요!