[Javascript] Optional Chaining / Nullish Coalescing
JS Optional Chaining, ?., ??, JS Nullish Optional Chaining
Nullish Coalescing
Nullish Assignment
정의
ES2020 도입의 두 가지 단축 문법.
?.(Optional Chaining) : 중간이 null/undefined 면undefined반환??(Nullish Coalescing) : 왼쪽이 null/undefined 면 오른쪽 사용
Optional Chaining ?.
user?.address?.city
// user 가 null/undefined 면 즉시 undefined
// 그 외에는 평소처럼 평가
// 옛 방식
user && user.address && user.address.city
메서드 호출
obj.method?.() // method 가 없으면 undefined, 있으면 호출
arr?.[0] // 배열 인덱스
fn?.(arg) // 함수 호출
함수 호출 우회
window.callback?.(data) // callback 정의되어 있을 때만 호출
Nullish Coalescing ??
const port = config.port ?? 3000;
// port 가 null 또는 undefined 면 3000, 그 외 그대로
|| 과의 차이:
0 || 'default' // 'default' (0 은 falsy)
0 ?? 'default' // 0 (null/undefined 만 default)
'' || 'fallback' // 'fallback'
'' ?? 'fallback' // ''
null || 'x' // 'x'
null ?? 'x' // 'x'
undefined || 'x' // 'x'
undefined ?? 'x' // 'x'
?? 가 더 명확. 0, ”, false 가 유효한 값일 때 ?? 필수.
Nullish Assignment ??=
obj.a ??= 'default';
// obj.a 가 null/undefined 면 'default' 할당
// 동등: obj.a = obj.a ?? 'default';
다른 단축:
||=: falsy 면 할당&&=: truthy 면 할당
config.timeout ||= 5000; // 0, '', false 도 덮어씀
config.timeout ??= 5000; // null/undefined 만 덮어씀
config.flag &&= 'enabled'; // truthy 면 'enabled' 로
자주 쓰는 패턴
API 응답 안전 접근
const username = response?.data?.user?.name ?? 'Guest';
옵션 객체 default
function api({ timeout, retries } = {}) {
timeout = timeout ?? 5000;
retries = retries ?? 3;
}
콜백 안전 호출
function process(items, { onSuccess, onError } = {}) {
try {
const result = doWork(items);
onSuccess?.(result);
} catch (e) {
onError?.(e);
}
}
배열 / Map 안전 lookup
const first = arr?.[0];
const value = map.get?.(key); // map 자체가 없을 수도
const found = users?.find?.(u => u.id === id);
React state
const name = user?.profile?.name ?? 'Anonymous';
함정
1. ?? 와 || 의 우선순위
a || b ?? c // ❌ SyntaxError (괄호 필요)
(a || b) ?? c // ✓
a || (b ?? c) // ✓
?? 와 || / && 는 함께 쓸 때 괄호 필수.
2. ?. 의 short-circuit
user?.greet().toUpperCase()
// user 가 undefined 면 → undefined.toUpperCase() ❌ TypeError
user?.greet()?.toUpperCase()
// 안전한 chaining
각 단계마다 ?. 필요.
3. 할당의 왼쪽에는 못 씀
obj?.x = 1 // ❌ SyntaxError
if (obj) obj.x = 1;
4. delete 와의 조합
delete obj?.prop
// obj 가 null 이면 무시, 있으면 delete
이건 가능.
5. 0, false, ” 의 처리
function getCount({ count = 10 } = {}) {
return count;
}
getCount({ count: 0 }); // 0 (default 적용 안 됨)
getCount({ count: null }); // null (default 적용 안 됨, ⚠️)
getCount({}); // 10
= default 는 undefined 만 적용. null 처리에 ?? 활용.
function getCount({ count } = {}) {
return count ?? 10;
}
getCount({ count: null }); // 10 ✓
?. 평가 흐름
flowchart LR
Expr["a?.b?.c 평가"] --> ChkA["null/undefined 검사 (a)"]
ChkA -->|"null/undefined"| UA["undefined 반환"]
ChkA -->|"값 있음"| GetB["a.b 접근"]
GetB --> ChkB["null/undefined 검사 (a.b)"]
ChkB -->|"null/undefined"| UB["undefined 반환"]
ChkB -->|"값 있음"| GetC["a.b.c 반환"]
short-circuit: null/undefined 발견 즉시 평가 종료. 이후 side-effect 있는 메서드 호출도 건너뜀.
let x = 0;
const obj = null;
obj?.method(x++); // x++ 실행 안 됨, x 는 여전히 0
TypeScript 와의 관계
?. 는 TypeScript 의 타입 좁히기와 함께 쓰면 효과적이다.
interface User {
profile?: {
name: string;
email?: string;
};
}
function greet(user: User) {
const name = user.profile?.name ?? 'Guest';
// name: string (undefined 제거)
const email = user.profile?.email ?? 'N/A';
// email: string
}
TypeScript 컴파일러는 ?. 결과를 T | undefined 로 추론한다.
declare const arr: string[] | null;
const first = arr?.[0]; // string | undefined
const upper = arr?.[0]?.toUpperCase(); // string | undefined
프레임워크 실제 패턴
React
function UserCard({ user }: { user?: User }) {
return (
<div>
<h1>{user?.name ?? 'Unknown'}</h1>
<p>{user?.profile?.bio ?? '소개 없음'}</p>
<button onClick={() => user?.onDelete?.()}>삭제</button>
</div>
);
}
API fetch 결과 안전 접근
async function loadUser(id) {
const res = await fetch(`/api/users/${id}`);
const data = await res.json();
return {
name: data?.user?.name ?? 'Unknown',
role: data?.user?.roles?.[0] ?? 'guest',
meta: data?.meta ?? {},
};
}
DOM 요소 안전 접근
document.querySelector('.btn')?.addEventListener('click', handler);
// 요소 없으면 에러 없이 건너뜀
const text = document.getElementById('title')?.textContent?.trim() ?? '';
연산자 우선순위 정리
| 표현식 | 결과 |
|---|---|
a?.b.c | a 가 null 이면 undefined, 아니면 a.b.c (b/c 는 보호 안 됨) |
a?.b?.c | 각 단계 독립 보호 |
a?.b ?? 'x' | a.b 가 null/undefined 면 'x' |
a == null ? 'x' : a.b | a?.b ?? 'x' 와 동등 |
NOTE
?. 는 null 과 undefined 만 단락 평가. 0, '', false 는 short-circuit 되지 않는다.
참고
이 글의 용어 (5개)
- [Javascript] 타입 변환 / 강제 변환javascript
- 정의 JavaScript 의 암묵적 형변환 (coercion). 연산자가 양쪽 피연산자의 타입을 자동으로 맞춘다. 명시적 변환과 구분. 3 가지 강제 변환 1. ToString …
- [Javascript] async/awaitjavascript
- 정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
- [Javascript] boolean / null / undefinedjavascript
- 정의 JavaScript 의 3 가지 primitive. - : , - : 의도적인 "없음" - : 자동으로 부여된 "없음" (초기화 안 됨) 사용 상황 | 상황 | 권장 값 /…
- [Javascript] Destructuringjavascript
- 정의 Destructuring 은 배열 또는 객체의 일부를 변수로 풀어내는 ES6 문법. 코드를 짧고 명시적으로. 객체 배열 함수 매개변수 자주 쓰는 idiom API 응답 분해…
- [Javascript] Error / try-catchjavascript
- 정의 JS 의 에러 처리. 로 던지고 로 잡는다. 내장 Error 타입과 사용자 정의 Error. throw / try / catch 는 에러 유무와 무관하게 실행. 내장 Err…
💬 댓글