1. 유령처럼 나타난 숫자 '0'의 공포
React 개발자라면 누구나 한 번쯤 겪는 "등골이 서늘한" 순간이 있습니다. 열심히 만든 쇼핑몰 장바구니 페이지, QA 테스트까지 완벽하게 마치고 배포했는데, 며칠 뒤 고객센터에서 연락이 옵니다. "화면 구석에 이상한 숫자 0이 떠 있어요. 이거 버그인가요?"
소스 코드를 아무리 뜯어봐도 <div>0</div>라고 하드코딩한 곳은 없습니다.
도대체 이 유령 0은 어디서 튀어나온 걸까요?
범인은 바로 우리가 가장 흔하게, 그리고 무심코 사용하는 논리 AND 연산자 (&&)입니다.
// 많은 개발자가 실수하는 코드 패턴
function Cart({ count }) {
return (
<div>
<h1>장바구니</h1>
{/* 개발자의 의도: 상품 개수가 있을 때만 뱃지를 보여주고 싶다 */}
{count && <Badge count={count} />}
</div>
);
}
이 코드는 count가 1, 2, 3일 때는 개발자의 의도대로 완벽하게 동작합니다. 뱃지가 잘 나옵니다.
하지만 count가 0이 되는 순간, 즉 장바구니가 비었을 때 문제가 발생합니다.
뱃지는 사라지지만, 대신 화면에 숫자 0이 덩그러니 출력됩니다.
React는 false, null, undefined는 화면에 렌더링하지 않지만, 숫자 0은 유효한 렌더링 대상(Text Node)으로 취급하기 때문입니다.
단순히 "보기 싫다"의 문제가 아닙니다. 레이아웃을 깨뜨릴 수도 있고, 사용자에게 "시스템 오류인가?"라는 불안감을 줄 수도 있습니다.
2. 범인은 React가 아니라 JavaScript다 - 단락 평가 (Short-Circuit Evaluation)
이 현상을 이해하려면 React를 탓하기 전에, JavaScript 언어 자체의 특성을 이해해야 합니다.
이것은 React의 버그가 아니라, JavaScript의 && 연산자 동작 방식(ECMAScript Spec) 때문입니다.
논리 AND 연산자(&&)는 단순히 true나 false를 반환하는 게 아닙니다.
"좌항이 Falsy하면 좌항을 반환하고, Truthy하면 우항을 반환한다"는 규칙을 따릅니다. 이를 단락 평가(Short-Circuit Evaluation)라고 합니다.
Falsy 값의 종류와 평가 더 알아보기
JavaScript에는 7가지 Falsy 값이 있습니다.
false0(숫자 0)-0(음수 0)0n(BigInt 0)""(빈 문자열)nullundefinedNaN
// Case 1: 좌항이 Truthy일 때 -> 우항 반환 (문제 없음)
true && "Hello" // "Hello"
10 && <div>Hi</div> // <div>Hi</div>
"text" && <Comp /> // <Comp />
// Case 2: 좌항이 Falsy일 때 -> 좌항 반환 (여기서 운명이 갈림)
// A. 안전한 Falsy 값들 (React가 무시함)
false && "Hello" // false
null && "Hello" // null
undefined && "Hello" // undefined
"" && "Hello" // "" (빈 문자열 - 경우에 따라 화면에 안 보일 뿐 렌더링은 됨)
// B. 위험한 Falsy 값 (React가 화면에 그림!)
0 && <div>Hi</div> // 0 <-- 범인 검거!
NaN && <div>Msg</div> // NaN <-- 공범 추가!
0은 JavaScript에서 Falsy한 값입니다.
그래서 && 연산자는 우항(<div>Hi</div>)을 쳐다보지도 않고, 즉시 좌항인 0을 리턴해버립니다.
React 입장에서 0은 그저 숫자일 뿐이므로, <span>0</span>처럼 화면에 그대로 렌더링해버리는 것이죠.
3. 해결책 - 안전한 조건부 렌더링 3가지 패턴
이 소름 끼치는 '0'의 저주에서 벗어나기 위한 3가지 패턴이 있습니다. 팀 스타일에 맞춰 골라 쓰세요.
3.1. 비교 연산자 사용하기 (가장 추천)
가장 명확하고 읽기 좋은 방법입니다. "0보다 클 때만 그린다"라는 의도가 코드에 명시적으로 드러납니다.
숫자 타입 변수에는 &&를 직접 붙이지 말고, 항상 비교식을 사용하세요.
{/* 명확한 의도: count가 0보다 클 때만 렌더링 */}
{count > 0 && <Badge count={count} />}
{/* 배열의 경우 */}
{items.length > 0 && <List items={items} />}
개인적으로 팀 프로젝트 코드 리뷰를 할 때 가장 권장하는 방식입니다. !!보다 훨씬 직관적이고 오해의 소지가 없습니다. 다른 개발자가 코드를 봤을 때 "아, 양수일 때만 그리는구나"라고 바로 이해할 수 있습니다.
3.2. 이중 부정 연산자 (!!, Double Bang) 사용하기
count 값을 강제로 boolean 타입으로 변환해주는 방법입니다.
!0은 true, !!0은 false가 되므로, 결과적으로 false && ...가 되어 React가 아무것도 렌더링하지 않게 됩니다.
{/* 0은 false로 변환되므로 안전함 */}
{!!count && <Badge count={count} />}
타이핑이 짧아서 선호하는 분들도 있지만, "느낌표 두 개가 뭐지?" 하고 멈칫하게 만드는 가독성 문제 때문에 싫어하는 분들도 있습니다. 팀 컨벤션이 있다면 따르세요.
3.3. 삼항 연산자 (Ternary Operator) 사용하기
조건이 거짓일 때 무엇을 렌더링할지 명시적으로 null을 지정합니다. if-else 구조가 필요할 때 가장 적합합니다.
{count ? <Badge count={count} /> : null}
특히 "데이터가 없을 때 '데이터 없음' UI를 보여줘야 한다"면 삼항 연산자가 정답입니다.
{count ? <Badge count={count} /> : <div className="text-gray-500">장바구니가 비었어요</div>}
4. 또 다른 복병: NaN (Not a Number)
0만 조심하면 될까요? 아닙니다. NaN도 있습니다.
NaN 역시 Falsy한 값이지만, 화면에 문자열 "NaN"으로 출력됩니다.
const percent = (loaded / total) * 100; // total이 0이면 0/0 = NaN 발생!
return (
<div>
{/* percent가 NaN이면 화면에 "다운로드 진행률: NaN%" 라고 뜸 */}
{percent && <span>다운로드 진행률: {percent}%</span>}
</div>
)
이런 참사를 막으려면 데이터의 유효성을 확실히 검사하거나, 위에서 말한 비교 연산자 (percent > 0) 패턴을 사용하는 것이 가장 안전합니다.
또는 Number.isNaN()이나 Number.isFinite()를 사용하여 데이터 무결성을 검증해야 합니다.
5. React Native 개발자는 특히 조심하세요! (앱이 죽습니다)
웹(Web) React에서는 기껏해야 0이라는 텍스트가 조금 못생기게 나오는 정도의 버그지만, React Native에서는 앱이 죽습니다(Crash).
React Native는 <View> 컨테이너 안에 텍스트를 그냥 넣을 수 없습니다. 반드시 <Text> 컴포넌트로 감싸야 합니다.
// React Native 코드
<View>
{/* count가 0이면 View 안에 숫자 0이 직접 들어감 -> 에러 발생! */}
{count && <ChildComponent />}
</View>
이 코드가 실행되자마자 다음과 같은 치명적인 에러와 함께 앱이 강제 종료됩니다.
Invariant Violation: Text strings must be rendered within a
<Text>component.
모바일 앱 개발에서 && 연산자 실수는 단순한 UI 버그가 아니라, 서비스 장애(Outage)로 이어질 수 있음을 명심해야 합니다. 사용자는 앱이 실행되자마자 꺼지는 경험을 하게 되고, 이는 앱 스토어 별점 테러로 이어질 수 있습니다.
6. 팀을 위한 가이드라인 - ESLint 설정하기
이런 실수를 개발자의 "주의력"에만 맡기는 것은 위험합니다. 사람은 누구나 실수를 하니까요. 도구의 힘을 빌려 원천 봉쇄하는 것이 좋습니다.
eslint-plugin-react 플러그인에 jsx-no-leaked-render 규칙이 있습니다. 이 규칙을 켜두면 숫자나 NaN이 렌더링될 수 있는 코드를 미리 경고해줍니다.
// .eslintrc.json
{
"rules": {
"react/jsx-no-leaked-render": [
"warn",
{ "validStrategies": ["ternary", "coerce"] }
]
}
}
이 설정을 추가하면, count && <Comp /> 같은 코드를 작성할 때 빨간 줄을 그어줍니다. "이봐, 이거 0 출력될 수도 있어!" 라고 알려주는 거죠. CI/CD 파이프라인에서 이 린트 에러를 잡도록 설정하면 배포 전에 100% 막을 수 있습니다.
7. 마무리 - "&&"를 너무 믿지 마세요
JavaScript의 && 연산자는 React의 JSX 조건부 렌더링을 위해 만들어진 전용 문법이 아닙니다. 범용적인 논리 연산자일 뿐입니다.
그 편리함 뒤에는 Falsy 값에 대한 함정이 숨어 있습니다.
Team Convention 제안:
"내 프로젝트에서는 숫자(number) 타입 변수에 대해서는 절대로 && 렌더링을 하지 않는다. 대신 > 0이나 삼항 연산자를 사용한다."
이 작은 습관 하나가 여러분의 퇴근 후 평화를 지켜줄 것입니다. 유령 '0'을 잡으러 다니는 시간, 아깝잖아요?