React의 컴포넌트에서 return;과 return null;의 차이는 뭘까 ?
회사에서 아래와 같은 상황으로

코드 리뷰가 제안된 PR이 있어서 스크럼때 react에서의 null과 undefined 차이에 대해 이야기를 하다가 내가 자바스크립트와 리액트에 대해 잘 알고 있나? 라는 생각이 문득 들어서 자세하게 찾아봤다.
return; VS return null;function EmptyComponent() { return; } | function EmptyComponent() { return null; } |
|---|---|
| 아무것도 렌더링되지 않는다 | 의도적으로 “아무것도 렌더링하지 않기” |
18에서는 null과 결과 동일 | |
| React 16에서는 에러 발생. | |
Error: Nothing was returned from render. This usually means a return statement is missing. |
return null;return; & return undefined;return문을 빼먹었을때 빠르게 찾아내기 위한 방어적 설계React 18
return; 과 return null;null과 undefined를 굳이 차별할 이유가 없어졌다.function test() {
return;
}
test() 를 출력했을 때의 결과물은 ?
보이는 것처럼 undefined를 리턴하고 있다.
이때,
별 차이 없다.
| React 16 | React 18 |
|---|---|
| 동작 여부의 문제(Error vs Success) | 리액트 내부의 Fiber 트리 구조에서 null 이나 undefined 모두 “자식 없음” 처리 |
undefined를 반환하면 앱이 멈추는 문제 발생 | 메모리 점유율이나 연산량에서 유의미한 차이 발생 X |
React 내부 개념을 대략적으로 간소화해보면 아래 구조를 가진다.
type ReactNode =
| JSX.Element
| string
| number
| null
| undefined
| boolean;
React 18에서 undefined를 empty node로 인정했고
렌더 트리에서 null과 동일하게 처리하기로 했다.
왜 이렇게 되었을지 알아보려면
React Reconciler를 확인해봐야 한다.
여기에 어떻게 화면이 바뀌고, 어떤 값을 무시하는지 담겨 있기 때문 !!

Reconciler의 3가지 핵심 역할으로는,,
react): useState, useEffectreact-reconciler): 화면을 어떻게 구성할지 ‘설계도’를 그리는 (Fiber 아키텍처)react-dom, react-native): 설계도대로 실제 화면에 그리는 작업자이제 react 내부 코드를 뜯어보자면,,,
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js
// packages/react-reconciler/src/ReactChildFiber.js
function reconcileChildFibersImpl(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const isUnkeyedUnrefedTopLevelFragment =
// 자바스크립트 객체인가
typeof newChild === 'object' && newChild !== null
// Fragment 타입인가 (<> ... </>)
&& newChild.type === REACT_FRAGMENT_TYPE
&& newChild.key === null
&& (enableFragmentRefs ? newChild.props.ref === undefined : true);
// 개발 환경에서만 검사 (배포했을때의 성능을 위해)
if (isUnkeyedUnrefedTopLevelFragment) {
validateFragmentProps(newChild, null, returnFiber);
newChild = newChild.props.children;
}
// 1. 객체 타입(컴포넌트, DOM 노드 등)인지 확인
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
const firstChild = placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
currentDebugInfo = prevDebugInfo;
return firstChild;
}
// 2. 문자열이나 숫자인 경우 (텍스트 노드 생성)
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
// $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint
'' + newChild,
lanes,
),
);
}
// 3. 위 조건에 해당하지 않는 경우 (null, undefined, boolean)
// 아무것도 반환하지 않음으로써 '빈 결과' 확정 !
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
boolean을 같이 처리하는 이유는 ?{isOpen && <Modal /> } 과 같은 조건부 렌더링 때문에
false라고 그대로 출력되면 불편하니까 boolean 값을 렌더링 트리에서 무시하도록 설계를 한 것이다.
다만, {count && <Component />} 를 사용하면 0이 글자로 찍힌다.
이때는 count 앞에 !! 연산자를 붙여주면 0이 글자로 찍히지 않는다.
null vs undefined리액트는 javascript로 만들어졌으니까 좀 더 찾아봤다.
const a = null;
const b = undefined;
null과 undefined의 차이는 뭘까 ?
undefined는 선언이 안된걸까 ? 라고 생각을 했다가
어쨋든 b 라는 변수를 선언해준거 아닌가 ?? 라는 생각도 같이 들었다.
하지만 const b; 는

이런식으로 에러가 난다
왜냐하면 const는 상수니까.
상수는 선언과 동시에 값이 결정되어야 한다. 나중에 바꿀 수 없다.
그렇다면
var a = null;
var b = undefined;
var c;
의 차이는 뭘까
c는 선언만 한거라서 안에 쓰레기 값이 할당되어 있다고 답변했다가
선언과 할당의 차이는 뭐냐는 질문을 받았다.
선언과 할당의 차이는 뭘까 ?!
자바스크립트에서는 var c; 라고 선언만 하면 자동으로 undefined 값을 넣는다.
자바스크립트에는 쓰레기 값이 존재하지 않는다.
var a = null; // "비어있음"을 명시적으로 '할당'
var b = undefined; // "정의되지 않음"을 명시적으로 '할당'
var c; // '선언'만 한 것. 엔진이 자동으로 undefined '할당'
b는 개발자가 의도적으로 넣은 것.
c는 아무것도 안 해서 엔진이 만들어서 생긴 값.
쓰레기 값이 생기지는 않는다. 쓰레기 값은 C언어에서만 !
| 구분 | undefined | null |
|---|---|---|
| 의미 | 값이 정의되지 않음 (초기 상태) | 값이 없음을 명시적으로 선언함 |
| 할당 | 변수 선언 후 값을 넣지 않았을 때 자동으로 할당됨 | 개발자가 "여기는 빈 값이야"라고 직접 할당함 |
| Type | typeof undefined === 'undefined' | typeof null === 'object' (JS의 유명한 설계 오류) |
왜 설계 오류인가를 찾아봤을 때
타입 태그에서 000을 객체로 보도록 만들어서 그렇다고 한다.
그래서 if (typeof v === 'object' && v !== null) 으로 방어 코드가 반드시 필요하다고 한다.
참고