진짜 사소하고 사소한데 방심하기 쉬운 ??와 ||의 차이

nullish vs falsy in js

이전에 기록해놨던 코드가 떠올랐다.

const [currentTeam, setCurrentTeam] = useState(
  user?.currentTeam ?? (teams.length > 0 ? teams[0].id : undefined)
);

처음엔 그냥 잘 넘어갔던 코드인데, 어느 순간 user.currentTeam이 빈 문자열인데도 기본값으로 바뀌어버리는 상황을 겪고 나서 이 코드가 눈에 밟히기 시작했다. 처음에는 그냥 ||를 써도 무방하다고 생각했지만, 의외로 작은 차이가 UX와 버그 발생에 직접적인 영향을 줄 수 있다는 것을 경험하게 되었다.

이 글은 그 경험을 바탕으로 ??, || 그리고 관련 문법을 정리해보려 한다.


??||, 뭘 기준으로 다르게 동작할까?

?? (null 병합 연산자)는 값이 null 또는 undefined인 경우에만 대체 값을 사용한다. 반면, || (논리 OR 연산자)는 모든 falsy 값(false, 0, "", null, undefined, NaN)을 없는 값으로 간주해 오른쪽 값을 사용한다.

예를 들어보자.

const name = userInput ?? "Guest"; // 입력이 null/undefined일 때만 "Guest"
const name = userInput || "Guest"; // 입력이 '', 0, false여도 "Guest"로 대체됨

userInput이 빈 문자열이면 ??는 그대로 ""를 사용하지만, ||는 이를 무시하고 "Guest"를 사용한다. 이 차이는 사용자 입력이나 설정값처럼 "의도적으로 비워놓은 값"을 허용할지 말지에 큰 영향을 준다.


실전에서 벌어지는 문제들

입력값이 무시되는 문제

const nickname = user.nickname || "Anonymous";

사용자가 일부러 ""를 입력했는데도 "Anonymous"로 나와버린다면, 꽤 어색한 상황이 된다. 이런 경우에는 ??가 더 적절하다.

숫자 0이 무시되는 문제

const pageSize = config.limit || 10;

limit이 0이라면? ||는 이를 falsy로 간주해 10을 사용하게 된다. 하지만 0은 분명히 의미 있는 값이다.

그래서?

const nickname = user.nickname ?? "Anonymous";
const pageSize = config.limit ?? 10;

처럼 null이나 undefined만 처리하고, 나머지는 존재하는 값으로 인정해야 한다.


Optional chaining (?.)과 함께 쓰면 더 강력해진다

자바스크립트에서는 중첩된 객체를 안전하게 접근하기 위해 다음과 같이 사용한다.

const teamId = user?.currentTeam ?? teams[0].id;

여기서 user?.currentTeamuser가 nullish일 경우 에러를 발생시키지 않고 undefined를 반환한다. 이 방식은 ??와 아주 잘 어울린다. 왜냐하면 이후에 ??로 기본값을 줄 수 있기 때문이다.


??= 연산자

ES2021에서 도입된 ??= 연산자는 아래처럼 쓸 수 있다.

config.timeout ??= 5000;

위 코드는 다음과 같다:

if (config.timeout === null || config.timeout === undefined) {
  config.timeout = 5000;
}

단순하고 명확하게 기본값을 설정하는 문법이며, 값이 이미 존재한다면 덮어쓰지 않는다.


사용 케이스

React 상태 초기화 시

const [limit, setLimit] = useState(config.itemsPerPage ?? 10);

→ 0도 허용하려면 ??, 무조건 fallback이 필요하다면 ||.


서버 응답 처리

const userName = response?.data?.user?.name ?? "비회원";

→ 객체가 깊이 중첩되어도 안전하게 기본값 제공.


안전한 프로퍼티 할당

settings.timeout ??= 3000;
settings.language ??= "ko";

→ 이미 값이 설정돼 있다면 건드리지 않음.


6. 결론: 어떤 상황에서 어떤 연산자를 써야 할까?

  • 값이 없을 수 있고, 0/false/""는 유효한 값일 때 → ??
  • 값이 비어 있거나 falsy하면 무조건 기본값으로 대체할 때 → ||

마무리

자바스크립트의 연산자들은 겉보기엔 비슷해 보여도, 실제 동작은 아주 다르다. ??||는 단순한 선택의 문제가 아니다. 이 둘의 차이는 기본값을 어떻게 정의하느냐, 어떤 값을 유효하다고 판단하느냐에 대한 개발자의 철학이 반영되는 지점이다.

실제로 코드를 리뷰하다 보면 ||로 처리한 부분이 0이나 false 때문에 문제가 되지 않을까? 싶은 경우가 아주아주 드물게 있었다. 그래서 최근에는 우선 ??부터 고려하고, 정말 ||가 필요한 상황만 선택적으로 사용하고 있다.

무심코 써왔던 연산자지만, 혹시나 이 글을 보게 되셨다면 한 번쯤은 고민해 보시길!!