TL;DR (1분 요약)

// 값이 ‘정말 없을 때’만 기본값 → ??=
settings.timeout ??= 3000;     // null/undefined일 때만 3000

// ‘비어있음(0, '', false)’도 대체하는 기본값 → ||=
user.theme ||= 'light';        // 0/''/false 포함해서 덮어씀

// 조건이 참일 때만 ‘결과로 재할당’ → &&=
user.isLoggedIn &&= hasRole(user, 'admin'); // 값도 바뀜(가드+재할당)
기억 법칙
  • 없을 때만(nullish)” → ??=
  • 비어있음까지 포함해 기본값” → ||=
  • 참이면 바꿔(가드+재할당)” → &&=

개념 정리: 단락 평가 + 할당

  • 논리 할당 연산자는 논리 연산자(||, &&, ??)와 =의 결합형: ||=, &&=, ??=
  • 단락 평가: 좌변만으로 결론이 나면 우변은 평가하지 않음 → 불필요한 계산/부작용 회피

1) ||= — falsy면 기본값으로 대체

user.theme ||= 'light';
// (의미상 유사) user.theme = user.theme || 'light';
  • 덮어쓰는 대상: undefined, null, '', 0, NaN, false (falsy)
  • 언제: “값이 비었으면 아무튼 기본값으로”
  • 주의: 0, '', false도 덮어쓴다. 이 값들을 유지하려면 ??= 사용.

2) ??= — nullish일 때만 기본값으로 대체

settings.timeout ??= 3000;
// if (settings.timeout === null || settings.timeout === undefined) settings.timeout = 3000;
  • 0, '', false유효값으로 보존
  • 언제: “정말 없을 때만 기본값으로”

3) &&= — 조건이 참이면 우변 결과로 재할당

user.isLoggedIn &&= checkPermissions(user);
// if (user.isLoggedIn) user.isLoggedIn = checkPermissions(user);
  • 의미: “참이면 우변 평가 → 그 결과로 갈아끼우기
  • 가드만 하고 값은 바꾸지 않으려면:
    if (cond) doSomething();
    // 또는
    cond && doSomething();
    
팁: obj.prop ||= v 는 좌변을 한 번만 평가한다는 점에서, obj.prop = obj.prop || v(좌변 두 번 평가)보다 게터/프록시 부작용을 줄일 수 있습니다.

실전 활용 6가지 (프론트엔드 문맥)

① 컴포넌트 props 기본값

props.showHelpText ??= true;      // null/undefined일 때만 true

② 전역 설정/환경값 덮어쓰기 방지

config.apiBase ??= '/api/v1';     // 기존 값 있으면 유지
// 0/''/false도 대체하고 싶다면 ||= 사용

③ 폼 입력 정리 + 기본값

form.username = form.username?.trim() ?? ''; // 문자열이면 trim, 없으면 ''

④ 컬렉션 초기화/메모이제이션

store.users ??= new Map();
store.users.get(id) ??= { id, name: '' };

cache[key] ??= expensiveCompute(key); // 없을 때만 계산

⑤ 토글/플래그 안전 처리

flags.darkMode ??= false;                // 미정이면 false
flags.darkMode &&= systemSupportsDarkMode(); // 가능할 때만 유지/갱신

⑥ React Query 옵션 병합(예시)

options.staleTime ??= 30_000;    // 명시 안 했으면 30s
options.enabled &&= isClient();  // 클라일 때만 쿼리 활성화

흔한 함정 & 회피법

1) 옵셔널 체이닝 좌변 금지

obj?.prop ||= 1;     // ❌ 문법 오류
obj ??= {};          // ✅ 먼저 객체 보장
obj.prop ??= 1;      // ✅ 그다음 기본값

2) 0/''/false 보존 여부

  • 보존하려면 ??=
  • “비어있음으로 간주해 대체”하려면 ||=

3) 타입 변화 주의(&&=)

  • x &&= yx를 y의 결과값으로 바꿉니다 → boolean이 객체/문자열로 바뀔 수 있음
  • 타입 의도 유지가 필요하면 조건문/별도 변수 사용

리팩터링 전/후 (손에 익히기)

Before
if (!user.theme) user.theme = 'light';
if (config.timeout === null || config.timeout === undefined) config.timeout = 5000;
if (user.isLoggedIn) user.isLoggedIn = hasRole(user, 'admin');
After
user.theme ||= 'light';                      // falsy면 대체
config.timeout ??= 5000;                     // nullish만 대체
user.isLoggedIn &&= hasRole(user,'admin');   // 참이면 재할당

미니 연습 문제 (응용)

  1. count0이면 그대로 두고, 없으면 0으로:
  2. label이 빈 문자열이면 'N/A'로 바꾸기:
  3. 로그인 상태일 때만 user.roleensureRole(user) 결과로 갱신:
  4. config.retry가 null/undefined면 3으로, 0은 유지: