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 &&= y
는 x를 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'); // 참이면 재할당
미니 연습 문제 (응용)
count
가0
이면 그대로 두고, 없으면0
으로:label
이 빈 문자열이면'N/A'
로 바꾸기:- 로그인 상태일 때만
user.role
을ensureRole(user)
결과로 갱신: config.retry
가 null/undefined면 3으로,0
은 유지: