조건부 렌더링
if문·삼항 연산자·논리곱·null 반환을 이용해 상태와 조건에 따라 표시할 컴포넌트와 요소를 선택합니다.
2장에서는 React의 핵심 개념인 컴포넌트와 props, state, 이벤트 처리를 익혔습니다. 3장에서는 조건부 렌더링과 리스트 렌더링, 폼 처리, 컴포넌트의 생명주기처럼 바뀌는 UI를 만드는 방법을 다루고, 마지막에 이를 모아 할 일 목록 앱을 만듭니다.
이 글의 주제는 조건부 렌더링(conditional rendering)입니다. 조건부 렌더링은 특정한 조건을 만족할 때만 컴포넌트나 UI 요소를 화면에 보여 주거나, 조건에 따라 다른 컴포넌트를 보여 주는 기법입니다. 사용자가 로그인했을 때만 마이 페이지 버튼을 보여 주거나, 데이터를 불러오는 동안 로딩 스피너를 보여 주는 것이 모두 조건부 렌더링의 예입니다. React에서 조건부 렌더링을 구현하는 방법은 여러 가지이며, 상황에 맞는 방법을 고르는 것이 중요합니다.
if 문 (조건문) 사용하기
가장 기본적인 조건부 렌더링 방법은 자바스크립트의 if 문을 쓰는 것입니다. if 문은 컴포넌트의 return 바깥에서 조건을 검사하고, 그 결과에 따라 다른 JSX를 고를 때 유용합니다. 로그인 상태에 따라 메시지를 바꿔 보여 주는 예제를 만들어 봅니다. 앞에서 만든 Vite 프로젝트에 이어서, src/components 폴더 안에 LoginControl.jsx 파일을 만듭니다.
import React, { useState } from 'react';
function GuestGreeting() {
return <h2>로그인 해주세요!</h2>;
}
function UserGreeting(props) {
return <h2>환영합니다, {props.userName}님!</h2>;
}
function LoginButton(props) {
return (
<button onClick={props.onClick}>
로그인
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
로그아웃
</button>
);
}
function LoginControl() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userName, setUserName] = useState(''); // 로그인 시 사용자 이름 저장
const handleLoginClick = () => {
setIsLoggedIn(true);
setUserName('김코딩'); // 예시 사용자 이름 설정
};
const handleLogoutClick = () => {
setIsLoggedIn(false);
setUserName('');
};
let button; // (1) 조건에 따라 렌더링할 버튼 변수 선언
if (isLoggedIn) { // (2) if 문을 사용하여 조건 검사
button = <LogoutButton onClick={handleLogoutClick} />;
} else {
button = <LoginButton onClick={handleLoginClick} />;
}
let greeting; // (3) 조건에 따라 렌더링할 인사말 변수 선언
if (isLoggedIn) {
greeting = <UserGreeting userName={userName} />;
} else {
greeting = <GuestGreeting />;
}
return (
<div style={{ border: '1px solid #0056b3', padding: '20px', margin: '20px', borderRadius: '8px', textAlign: 'center' }}>
{greeting} {/* (4) JSX 내에서 변수 사용 */}
{button}
</div>
);
}
export default LoginControl;LoginControl은 isLoggedIn state에 따라 greeting 변수에 GuestGreeting이나 UserGreeting을, button 변수에 LoginButton이나 LogoutButton을 담습니다. 그리고 return 안에서는 이 변수들을 중괄호 {}에 넣어 렌더링합니다. 이제 App.jsx에서 LoginControl을 씁니다.
import React from 'react';
import './App.css';
import LoginControl from './components/LoginControl'; // LoginControl 컴포넌트 불러오기
function App() {
return (
<div className="App">
<h1>React 조건부 렌더링 예시</h1>
<LoginControl />
</div>
);
}
export default App;브라우저에서 로그인과 로그아웃 버튼을 눌러 보세요. 처음에는 “로그인 해주세요!”와 “로그인” 버튼이, 로그인 버튼을 누른 뒤에는 “환영합니다, 김코딩님!”과 “로그아웃” 버튼이 보이고, 로그아웃하면 다시 처음 표시로 돌아옵니다. 이벤트 처리 함수가 다음 상태를 요청하면, 다음 렌더에서 두 if 문이 같은 isLoggedIn을 읽어 인사말과 버튼을 함께 고릅니다.
여기서 로그인은 화면의 분기를 연습하기 위한 로컬 상태일 뿐, 서버에 비밀번호를 보내거나 사용자를 인증하는 코드는 없습니다. 버튼의 표시를 바꾸는 일과 실제 인증은 구별해야 합니다. if 문은 여러 컴포넌트를 골라야 하거나, 조건이 복잡해 읽기 쉽게 나누고 싶을 때 특히 유용합니다.
인라인 조건부 렌더링 (삼항 연산자 ? :)
JSX 안에서 간단히 조건을 처리하고 싶을 때는 삼항 연산자(condition ? true : false)를 씁니다. if 문보다 간결하게 조건을 표현할 수 있어 자주 쓰입니다. 2장에서 만든 MessageChanger 컴포넌트를 삼항 연산자로 조건부 렌더링을 하도록 고쳐 봅니다. 앞의 App.jsx는 LoginControl만 렌더링하므로, import MessageChanger from './components/MessageChanger';를 더하고 <LoginControl /> 아래에 <MessageChanger />를 넣어 함께 확인합니다.
import React, { useState } from 'react';
function MessageChanger() {
const [showMessage, setShowMessage] = useState(false); // 메시지 표시 여부 상태
const handleToggleMessage = () => {
setShowMessage(!showMessage); // 상태 반전
};
return (
<div style={{ border: '1px dashed #999', padding: '15px', margin: '20px', borderRadius: '8px' }}>
<h2>메시지 토글</h2>
{/* true이면 표시 메시지, false이면 숨김 안내 문장을 보여줌 */}
{showMessage ? ( // (1) 삼항 연산자 사용
<p style={{ fontSize: '20px', fontWeight: 'bold', color: '#28a745' }}>
지금 메시지가 보입니다!
</p>
) : (
<p style={{ fontSize: '16px', color: '#6c757d' }}>
메시지가 숨겨져 있습니다.
</p>
)}
<button onClick={handleToggleMessage} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer' }}>
{showMessage ? '메시지 숨기기' : '메시지 보기'} {/* 버튼 텍스트도 조건부 렌더링 */}
</button>
</div>
);
}
export default MessageChanger;showMessage state의 값에 따라 서로 다른 <p> 태그가 렌더링됩니다. 처음에는 showMessage가 false이므로 “메시지가 숨겨져 있습니다.”와 “메시지 보기” 버튼이 보이고, 버튼을 누르면 “지금 메시지가 보입니다!”와 “메시지 숨기기” 버튼으로 바뀝니다. 삼항 연산자는 이처럼 조건이 간단하고 참과 거짓 두 경우 가운데 하나를 렌더링할 때 잘 맞습니다.
논리 연산자 && (AND) 사용하기
특정 조건이 참일 때만 어떤 요소를 렌더링하고, 거짓일 때는 아무것도 렌더링하지 않으려면 논리 AND(&&) 연산자를 씁니다. JSX에서 null, false, undefined는 화면에 아무것도 그리지 않는다는 특성을 이용한 방법입니다. 자바스크립트에서 true && expression은 expression으로, false && expression은 false로 평가됩니다. 다만 왼쪽에 숫자 0이 오면 0이 그대로 렌더링되므로, 개수 조건은 count > 0처럼 불리언으로 만들어야 합니다.
경고 메시지를 보이고 숨기는 예제를 만들어 봅니다.
import React, { useState } from 'react';
function WarningBanner({ warn }) { // warn props를 받음
if (!warn) { // warn이 false면 null을 반환하여 아무것도 렌더링하지 않음
return null;
}
return (
<div style={{ backgroundColor: '#ffc107', color: 'white', padding: '10px', borderRadius: '5px', marginBottom: '15px' }}>
경고! 데이터가 안전하지 않습니다.
</div>
);
}
function AlertControl() {
const [showWarning, setShowWarning] = useState(true);
const handleToggleClick = () => {
setShowWarning(prevWarn => !prevWarn);
};
return (
<div style={{ border: '1px solid #dc3545', padding: '20px', margin: '20px', borderRadius: '8px', textAlign: 'center' }}>
<h2>경고 배너 제어</h2>
{/* showWarning이 true일 때만 WarningBanner 컴포넌트가 렌더링됨 */}
{showWarning && <WarningBanner warn={true} />} {/* (1) 논리 AND 연산자 사용 */}
<button onClick={handleToggleClick} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer' }}>
{showWarning ? '경고 숨기기' : '경고 보이기'}
</button>
</div>
);
}
export default AlertControl;이 파일은 WarningBanner와 AlertControl을 함께 정의하고 AlertControl을 기본으로 내보내므로, App.jsx에서는 import AlertControl from './components/WarningBanner';로 불러와 <AlertControl />을 씁니다. showWarning && <WarningBanner warn={true} />는 showWarning이 true일 때만 WarningBanner를 렌더링하고, false일 때는 아무것도 렌더링하지 않습니다. 처음에는 노란 경고 배너와 “경고 숨기기” 버튼이 보이고, 버튼을 누르면 배너가 사라지며 버튼이 “경고 보이기”로 바뀝니다. 이처럼 &&는 조건을 만족할 때만 UI 요소를 보여 주고 그렇지 않으면 숨기고 싶을 때 간결하게 쓸 수 있습니다.
컴포넌트 렌더링 막기: null 반환
컴포넌트가 조건을 검사한 뒤 화면에 보여 줄 결과를 비워야 할 때가 있습니다. 이럴 때는 컴포넌트의 return에서 null을 돌려주면 되며, 앞의 WarningBanner가 이미 이 방법을 썼습니다.
function WarningBanner({ warn }) {
if (!warn) { // warn이 false면
return null; // 아무것도 렌더링하지 않음
}
// ... (JSX 렌더링 코드)
}null을 돌려주면 컴포넌트 함수는 실행되지만 화면에 보여 줄 자식 결과가 없어, 그 컴포넌트의 출력이 완전히 숨겨집니다. 다만 이것은 부모 트리에서 컴포넌트를 없애는 것과는 다르며, 컴포넌트의 상태가 유지될 수 있다는 점에서 차이가 납니다.
더 깊이: 분기가 바뀌어도 state가 남는 경우
React가 state를 이어 줄지 정하는 기준은 JSX에 적은 분기가 아니라, 렌더 결과의 트리에서 컴포넌트가 놓인 위치와 그 타입입니다. 예를 들어 isFancy ? <Counter isFancy={true} /> : <Counter isFancy={false} />는 JSX에서는 서로 다른 두 줄이지만, 어느 쪽이든 부모의 같은 자리에 같은 Counter가 오므로 React는 같은 컴포넌트로 보고 state를 유지합니다. 반대로 같은 자리에 다른 타입이 오면, 예를 들어 Counter가 있던 자리에 p가 오면 React는 Counter와 그 아래 트리 전체의 state를 버립니다(React 문서의 state 보존과 초기화).
그래서 대화 상대를 바꿨는데 입력 중이던 메시지가 그대로 남는 것 같은 문제가 삼항 연산자에서 생기기 쉽습니다. 같은 자리에서 state를 새로 시작하게 하는 방법은 두 가지입니다. 하나는 {isPlayerA && <Counter person="Taylor" />}와 {!isPlayerA && <Counter person="Sarah" />}처럼 && 두 개로 나눠 서로 다른 자리에 렌더링하는 것이고, 다른 하나는 두 분기에 key="Taylor"와 key="Sarah"처럼 서로 다른 key를 주는 것입니다. key는 목록에서만 쓰는 속성이 아니라, 부모 안에서 컴포넌트의 순서 대신 그 자리를 정하는 이름으로도 쓰입니다.
조건부 렌더링 방법 선택 가이드
어떤 조건부 렌더링 방법을 고를지는 분기의 범위, 대신 보여 줄 화면이 필요한지, JSX 안에서 얼마나 잘 읽히는지에 따라 달라집니다. if 문은 조건이 복잡하거나 조건에 따라 완전히 다른 여러 JSX 블록을 렌더링해야 할 때, 그리고 return 바깥에서 로직을 나눠 읽기 쉽게 하고 싶을 때 씁니다. 삼항 연산자는 참일 때와 거짓일 때 모두 보여 줄 내용이 있고 JSX가 비교적 간결할 때, JSX 안에서 바로 조건을 표현하기에 가장 알맞습니다. && 연산자는 조건이 참일 때만 요소를 보여 주고 거짓일 때는 아무것도 보여 주지 않을 때 한 줄로 간결하게 쓸 수 있고, null 반환은 특정 조건에서 컴포넌트의 출력을 비우고 싶을 때 씁니다.
CONDITIONAL OUTPUT
조건식은 원하는 표시 결과에 맞춰 읽기 쉽게 고릅니다
if·삼항식·논리곱은 JSX를 선택하는 문법이고 null은 비어 있는 반환 결과입니다. 순서대로 실행하는 네 단계가 아니며, 본문의 로그인과 경고 예에서 표시할 결과를 먼저 정합니다.
if / 삼항식 · 둘 중 하나
isLoggedIn: false → GuestGreeting
isLoggedIn: true → UserGreetingLoginControl은 인사말과 버튼을 각각 if로 선택합니다. MessageChanger는 참·거짓 모두 p와 버튼 문구가 있어 삼항식이 간결합니다.
&& / null · 비어 있는 결과
showWarning && <WarningBanner />
if (!warn) return null;첫 식은 부모가 배너를 포함할지 결정합니다. null 반환은 함수가 실행돼도 자식 화면을 비웁니다. 부모 트리에서 제거하는 것과 상태 보존이 다를 수 있습니다.
숫자 0 && JSX는 0을 표시합니다. 개수 조건은 count > 0처럼 불리언으로 만듭니다. 복잡한 중첩은 변수나 컴포넌트로 분리합니다.
위 그림은 이 네 방법을 고르는 기준을 판단의 흐름으로 정리합니다. 렌더 조건이 여러 단계의 검사를 거쳐야 한다면 if를 쓰고, 그렇지 않다면 원하는 표시 결과를 봅니다. 참과 거짓 가운데 하나를 보여 줄 때는 삼항식(삼항 연산자로 쓴 식), 참일 때만 보여 줄 때는 &&, 아무것도 보여 주지 않을 때는 null을 고릅니다. 그림의 설명처럼 이것은 반드시 차례로 실행하는 단계가 아니라 문법을 고를 때 참고하는 기준이며, null은 if나 삼항식의 결과로도 돌려줄 수 있습니다.
그림의 아래쪽은 본문의 예제와 짝을 짓습니다. LoginControl은 isLoggedIn이 false이면 GuestGreeting을, true이면 UserGreeting을 if로 고르며, 그림의 if 칸에 적힌 조기 반환 대신 변수에 담아 고르는 방식을 씁니다. 조기 반환은 WarningBanner의 if (!warn) return null;이 보여 줍니다. MessageChanger는 참과 거짓 모두 p와 버튼 문구가 있어 삼항식이 간결합니다. showWarning && <WarningBanner />는 부모가 배너를 넣을지를 정하고, null 반환은 함수가 실행되더라도 자식 화면을 비웁니다. 마지막으로 숫자 0과 &&를 함께 쓰면 0이 보이므로 개수 조건은 불리언으로 만들고, 중첩이 복잡해지면 변수나 컴포넌트로 나눕니다.
이 방법들을 쓰면 props와 state에 따라 컴포넌트가 보여 줄 내용을 조정할 수 있습니다. 조건문의 문법과 0의 주의점은 React 문서의 조건부 렌더링에서도 확인할 수 있습니다.
정리
if문은return바깥에서 조건을 검사해 변수에 JSX를 담거나 일찍 반환할 때 씁니다.- 삼항 연산자는 참과 거짓 두 결과 가운데 하나를 JSX 안에서 고를 때 씁니다.
&&는 조건이 참일 때만 요소를 보여 주며, 왼쪽에 숫자 0이 오면 0이 그대로 보이므로count > 0처럼 불리언을 씁니다.- 컴포넌트가
null을 돌려주면 출력은 비지만, 부모 트리에서 컴포넌트를 없애는 것과는 다릅니다. - 화면에서 로그인 버튼을 바꾸는 로컬 상태는 실제 인증과 별개입니다.
연습 문제
1. 저장하는 이름 바꾸기
handleLoginClick에서 저장하는 이름만 “지수”로 바꾸고 로그인하면, 인사말과 버튼은 어떻게 보일까요?
해설 보기
로그인 상태가 참이므로 “환영합니다, 지수님!”과 “로그아웃” 버튼이 보입니다. 이름을 바꿔도 버튼을 고르는 조건은 그대로입니다.
2. 새 메시지가 0개일 때
새 메시지의 개수 count가 0일 때 count && <p>새 메시지</p>와 count > 0 && <p>새 메시지</p>는 각각 무엇을 보여 줄까요?
해설 보기
첫 번째 식은 자바스크립트에서 0으로 평가되므로 화면에 숫자 0이 나타납니다. 두 번째 식은 false로 평가되므로 보여 줄 자식이 없습니다. &&가 언제나 불리언을 돌려준다고 가정하지 마세요.
3. 부모가 지우는 것과 자식이 비우는 것
원래의 AlertControl에서 경고 숨기기를 누르면 부모가 WarningBanner를 호출할까요? 부모가 대신 항상 <WarningBanner warn={showWarning} />를 돌려준다면, 거짓 조건은 어디에서 처리될까요?
해설 보기
원래 코드에서는 부모의 && 식이 거짓이 되어 그 자식이 트리에서 빠지므로, WarningBanner는 호출되지 않습니다. 바꾼 코드에서는 WarningBanner 함수가 호출되어 warn을 검사하고 return null을 실행합니다.
함수가 빈 결과를 돌려주는 것과 부모가 컴포넌트 자체를 없애는 것은 다릅니다. 두 방식 모두 화면에서는 배너가 사라지지만, 컴포넌트가 트리에 남아 있는지는 다릅니다.
다음 글 리스트와 키에서는 배열 데이터를 화면의 목록으로 렌더링하는 방법과, React가 목록의 항목을 구별하는 key를 배웁니다.