본문으로 건너뛰기

옵셔널 체이닝

· 약 4분
Maya Armyanova ([@Zmayski](https://twitter.com/Zmayski)), 옵셔널 체인의 브레이커

JavaScript에서 긴 속성 접근 체인은 오류를 일으키기 쉽습니다. 이 중 하나라도 null 또는 undefined(“nullish” 값으로 알려짐)일 수 있기 때문입니다. 각 단계에서 속성 존재 여부를 확인하는 것은 쉽게 깊이 중첩된 if-문 구조 또는 속성 접근 체인을 복제하는 긴 if-조건으로 바뀔 수 있습니다:

// 오류 발생 가능 - 예외를 발생시킬 수 있음.
const nameLength = db.user.name.length;

// 덜 오류 발생 가능하지만 가독성이 떨어짐.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;

위 코드는 삼항 연산자를 사용하여도 가독성에 크게 도움이 되지 않습니다:

const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);

옵셔널 체이닝 연산자 소개

분명 이런 코드를 작성하고 싶지 않을 것입니다. 그래서 다른 대안을 사용하는 것이 바람직합니다. 일부 언어는 이 문제를 해결하기 위해 “옵셔널 체이닝”이라는 기능을 제공합니다. 최근의 스펙 제안에 따르면, “옵셔널 체인은 하나 이상의 속성 접근 및 함수 호출로 이루어진 체인으로, 첫 번째는 ?. 토큰으로 시작합니다.”

새로운 옵셔널 체이닝 연산자를 사용하면 위의 예제를 다음과 같이 다시 쓸 수 있습니다:

// 여전히 오류를 검사하지만 훨씬 더 가독성이 좋음.
const nameLength = db?.user?.name?.length;

db, user, 또는 nameundefined 또는 null이면 어떻게 될까요? 옵셔널 체이닝 연산자를 사용하면 JavaScript는 nameLengthundefined로 초기화하며 예외를 던지지 않습니다.

이 동작은 또한 우리가 if (db && db.user && db.user.name)로 검사하는 것보다 더 강력합니다. 예를 들어, name이 항상 문자열임이 보장된다면, name?.lengthname.length로 변경할 수 있습니다. 그 경우, name이 빈 문자열이라면, 여전히 0 길이를 올바르게 얻을 수 있습니다. 이는 빈 문자열이 falsy 값이기 때문인데, 이는 if 문에서 false로 작동합니다. 옵셔널 체이닝 연산자는 이와 같은 일반적인 버그 소스를 해결합니다.

추가 문법 형식: 호출과 동적 속성

옵셔널 메서드 호출을 위한 연산자 버전도 있습니다:

// 관리자 사용자만을 위한 선택적 메서드를 인터페이스에 확장
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;

이 문법은 약간 예상치 못한 느낌을 줄 수 있는데, ?.()가 실제 연산자이며 이는 그 앞의 표현식에 적용됩니다.

연산자의 세 번째 사용법은 바로 동적 속성 접근으로, 이는 ?.[]를 통해 이루어집니다. 이는 괄호 안의 인수로 참조된 값을 반환하거나 값을 얻을 객체가 없는 경우 undefined를 반환합니다. 다음은 위에서의 예제를 따른 가능한 사용 사례입니다:

// 정적 속성 접근의 성능을
// 동적으로 생성된 속성 이름으로 확장
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;

이 마지막 형태는 배열을 선택적으로 인덱싱하는 것도 가능합니다. 예:

// `usersArray`가 `null` 또는 `undefined`라면,
// `userName`은 우아하게 `undefined`로 평가됩니다.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;

옵셔널 체이닝 연산자는 널리쉬 병합 ?? 연산자와 결합하여 undefined가 아닌 기본값이 필요할 때 사용할 수 있습니다. 이는 깊은 속성 접근을 안전하게 지정된 기본값으로 수행할 수 있게 하며, 이전에는 lodash의 _.get 같은 사용자 라이브러리가 필요했던 일반적인 사용 사례를 해결합니다:

const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};

{ // lodash 사용:
const firstName = _.get(object, 'names.first');
// → 'Alice'

const middleName = _.get(object, 'names.middle', '(no middle name)');
// → '(no middle name)'
}

{ // 옵셔널 체이닝 및 널리쉬 병합 사용:
const firstName = object?.names?.first ?? '(no first name)';
// → 'Alice'

const middleName = object?.names?.middle ?? '(no middle name)';
// → '(no middle name)'
}

옵셔널 체이닝 연산자의 속성

옵셔널 체이닝 연산자는 단락 평가(short-circuiting), 중첩(stacking), _옵셔널 삭제(optional deletion)_라는 몇 가지 흥미로운 속성을 가지고 있습니다. 각 속성을 예제와 함께 살펴보겠습니다.

_단락 평가_란 옵셔널 체이닝 연산자가 조기에 반환하면 나머지 표현식을 평가하지 않는 것을 의미합니다:

// `age`는 `db`와 `user`가 정의된 경우에만 증가합니다.
db?.user?.grow(++age);

_스태킹_은 여러 개의 옵셔널 체이닝 연산자를 속성 접근 시퀀스에 적용할 수 있다는 것을 의미합니다:

// 옵셔널 체인은 다른 옵셔널 체인에 뒤따라 올 수 있습니다.
const firstNameLength = db.users?.[42]?.names.first.length;

그러나 단일 체인에서 여러 개의 옵셔널 체이닝 연산자를 사용하는 것은 신중해야 합니다. 값이 nullish가 아니라는 것이 보장되는 경우 해당 값에 ?.로 속성을 접근하는 것은 권장되지 않습니다. 위의 예에서 db는 항상 정의되는 것으로 간주되지만 db.usersdb.users[42]는 정의되지 않을 수 있습니다. 데이터베이스에 해당 사용자가 있는 경우 names.first.length는 항상 정의되는 것으로 간주됩니다.

_옵셔널 삭제_는 delete 연산자가 옵셔널 체인과 결합될 수 있음을 의미합니다:

// `db.user`는 `db`가 정의된 경우에만 삭제됩니다.
delete db?.user;

자세한 내용은 제안의 Semantics 섹션에서 확인할 수 있습니다.

옵셔널 체이닝 지원