본문으로 건너뛰기
김신건의 로그

[Javascript] Optional Chaining / Nullish Coalescing

· 수정 · 📖 약 1분 · 386자/단어 #javascript #optional-chaining #nullish #syntax
JS Optional Chaining, ?., ??, JS Nullish

정의

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.ca 가 null 이면 undefined, 아니면 a.b.c (b/c 는 보호 안 됨)
a?.b?.c각 단계 독립 보호
a?.b ?? 'x'a.b 가 null/undefined 면 'x'
a == null ? 'x' : a.ba?.b ?? 'x' 와 동등

NOTE

?.nullundefined 만 단락 평가. 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…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기