컴포넌트 함수 내부에서 특정 값에 따라 선택적으로 렌더링하는 것을 조건부 렌더링이라고 한다. 가독성을 높이는 조건부 렌더링 방법과 특징을 알아보고 어떻게 가장 최적화된 코드를 작성할 수 있는지 알아보자.
function GreetingA({ isLogin, name }) {
if (isLogin) {
return <p>{`${name}님 안녕하세요.`}</p>;
} else {
return <p>권한이 없습니다.</p>;
}
}
function GreetingB({ isLogin, name }) {
return <p>{isLogin ? `${name}님 안녕하세요.` : '권한이 없습니다.'}</p>
}
리액트 공식 문서에서 소개하는 가장 기본적인 패턴이다. GreetingA 함수의 경우 if문 조건인 로그인 여부에 따라, 어떤 요소를 렌더링할지 결정한다. 이 패턴은 컴포넌트의 일부분만 선택적으로 렌더링하기 적합하지 않다.
GreetingB 함수는 삼항연산자를 활용해서 조건부렌더링을 한다. if else 패턴의 단점인 부분 렌더링이 가능하도록 개선한 패턴이다. GreetingB의 코드가 더 간결하고 p 태그가 한 번만 등장해서 GreetingA보다 좋은 것 같지만, 매번 그렇지는 않다.
컴포넌트를 다른 조건에 따라 다르게 렌더링하는 게 아니라, 컴포넌트 렌더링 여부 자체를 결정할 때는 불필요하게 null을 반환하는 코드를 작성해야 한다.
function GreetingB({ isLogin, name }) {
return (
<React.Fragment>
{isLogin ? <p>`${name}님 안녕하세요.`</p> : null }
</React.Fragment>
)
}
null을 반환한다고 해서 심각한 성능 저하가 발생하지는 않기 때문에 그대로 사용해도 되지만 이 부분은 && 패턴으로 더 효율적으로 개선할 수 있다.
조건부 렌더링을 구현할 때는 삼항 연산자가 유용한 경우도 있지만, 대부분 && 연산자가 가독성이 더 좋다. && 패턴은 (선행 조건) && (후행 조건)
논리 연산자는 선행 조건이 참이어야만 후행 조건을 평가하고, 후행 조건을 평가한 결과를 반환한다.
function Greeting({ isLogin, name, cash }) {
return (
<div>
저희 사이트에 방문해주셔서 감사합니다.
{
isLogin && (
<div>
<p>{name}님 안녕하세요.</p>
<p>현재 보유하신 금액은 {cash}원입니다.</p>
</div>
)
}
</div>
)
}
컴포넌트의 일부분만 선택적으로 렌더링할 수 있을 뿐만 아니라, 코드의 끝에 null을 생략해도 되기 때문에 가독성이 좋아진다.
실제 프로젝트에서는 여러 개의 조건에 따라 다양한 상태를 렌더링해야 하는 경우가 많은데 이런 경우 && 패턴이 의미있게 사용될 수 있다. 다음 코드는 메인페이지에서 보여줄 문구를 조건에 따라 &&패턴으로 표현한 결과다. 조건은 다음과 같다.
- 이벤트 기간에는 개인정보를 생략한 채, 이벤트 문구를 보여준다.
- 이벤트 기간이 아닌 경우, 로그인을 했더라도 캐시가 10만이 넘으면 해킹한 사람이므로 개인정보를 보여주지 않는다.
function Greeting() {
return (
<div>
저희 사이트에 방문해주셔서 감사합니다.
{isEvent && (
<div>
<p>오늘의 이벤트를 놓치지 마세요.</p>
<button onClick={onClickEvent}>이벤트 참여하기</button>
</div>
)}
{!isEvent &&
isLogin &&
cash <= 100000 && (
<div>
<p>{name}님 안녕하세요.</p>
<p>현재 보유하신 금액은 {cash}원입니다.</p>
</div>
)}
</div>
)
}
이벤트 여부 또는 사용자의 상태에 따라 조건이 복잡한데도 코드가 크게 두 그룹이라는 점, 그리고 각 그룹의 조건들이 한눈에 잘 드러난다.
숫자 타입
인 경우, 0은 false
고, 문자열 타입
인 경우 빈 문자열도 false
다. 다음 코드는 && 연산자를 잘못 사용한 예시다.// 잘못된 예시
<div>
{cash && <p>{cash}원 보유 중</p>}
{memo && <p>{200 - memo.length}자 입력 가능</p>}
</div>
cash가 숫자 형태인데 0이면 '0원 보유 중'이 출력되지 않을 것이다. 또 memo에 아무 내용이 없으면 '200자 입력 가능'이라고 출력되어야 하는데 출력되지 않는다. 위 경우에는 명확하게 undefined, null이 아닌 경우라고 표현해야 한다. 다음은 의도한 대로 동작하도록 고친 코드다.
// 올바른 예시
<div>
{cash !== null && <p>{cash}원 보유 중</p>}
{memo !== null && <p>{200 - memo.length}자 입력 가능</p>}
</div>
cash !== null 은 cash가 undefined가 아니고 null도 아니면 참이 된다.
<div>
{
students && students.map((student, idx) => {
<div key={idx}>{student}</div>
})
}
</div>
students 배열의 기본값으로 빈 배열을 설정해주지 않는다면 students && 또는 students.length > 0 && 를 입력해주어야 map 돌리는 대상이 undefined라는 오류창을 피할 수 있다.
<div>
{
products.map((product, idx) => {
<div key={idx}>{product}</div>
})
}
</div>
product 배열의 기본값을 빈 배열로 설정하면 코드가 간결해진다.
Switch case는 if else문과 비슷한 방법으로 코드를 작성할 수 있고, 마찬가지로 부분 렌더링이 어렵다. 이 방법은 TypeScript와 작성되었을 때 시너지가 난다고 한다.