옵셔널 체이닝(optional chaining) 연산자 (?.)을 사용하면 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있다.
▷ 구문
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
옵셔널 체이닝 연산자는 결국 참조가 누락될 가능성이 있는 경우 연결된 속성으로 접근할 때 더 짧고 간단한 표현식으로 사용할 수 있다.
즉, '앞'의 평가 대상이 undefined 나 null이면 평가를 멈추고 undefined를 반환한다.
예제를 통해 알아보자.
옵셔널 체이닝이 등장하기 이전
let user = {}; // 주소 정보가 없는 사용자
// 1. 에러 발생
alert(user.address.street); // TypeError: Cannot read property 'street' of undefined
// 2. 에러 발생을 방지하기 위함
alert( user && user.address && user.address.street ); // undefined
예제 코드는 중첩 객체를 대상으로 작성되었는데,
중첩 객체의 특정 프로퍼티에 접근하기 위해 거쳐야 할 구성요소들을 AND로 연결해 실제 해당 객체나 프로퍼티가 있는지 확인하는 방법을 사용해야 했다.
당연히 중첩되면 중첩될수록 점점 가독성이 떨어지고 코드가 너무 길어진다는 단점이 존재한다.
옵셔널 체이닝 등장 후
let user = {}; // 주소 정보가 없는 사용자
alert( user?.address?.street ); // undefined
사용법 자체는 위에서 구문으로 안내한 것처럼 간단하다.
앞의 평가 대상이 존재하는지, 안 하는지를 체크해주고 존재할 경우 다음 평가를 진행하고 아니면 undefined를 반환한다.
분명 사용함에 있어 편리한 것은 맞지만,
너무 남용하게 된다면 에러를 조기에 발견하거나 디버깅이 어려워지는 경우가 생긴다.
다음 예제를 살펴보자
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
이 예제는 잘못된 사용방법이다.
옵셔널 체이닝은 존재하지 않아도 괜찮은 대상에만 사용하는 것이 좋다.
코드상 user는 논리상 반드시 존재해야 하고 오히려 address는 필수 값이 아니다.
user가 반드시 존재해야 하기 때문에 user에 값이 없다면 에러를 반환하는 게 맞다.
그렇지 않다면 에러 발생 없이 다음으로 넘어가 분명 어디서 인가 에러가 발생하게 될 텐데 그 에러를 발견하는 게 힘들어 질 수 있다.
그렇기 때문에 다음과 같이 사용하도록 하는게 바람직하다.
let user = {}; // 주소 정보가 없는 사용자
alert( user.address?.street ); // undefined
단락 평가
옵셔널 체이닝의 재밌는 점은 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춘다는 것이다.
이런 평가 방법을 단락 평가(short-circuit)라고 부른다.
그렇기 때문에 함수 호출을 비롯한 ?. 오른쪽에 있는 부가 동작은 평가가 멈췄을 때 더는 일어나지 않는다.
let user = null;
let x = 0;
user?.sayHi(x++); // 아무 일도 일어나지 않습니다.
alert(x); // 0, x는 증가하지 않습니다.
user에서 존재하지 않는다 라는 것을 판단이 완료됐기 때문에 평가가 더 이상 진행되지 않아 우측에 있는 함수 호출과 x++가 동작하지 않아서 결국 x는 0으로 존재하는 것이다.
?.()와 ?.[]
옵셔널 체이닝은 존재하지 않을 수 있는 메서드를 호출할 때 사용하거나 속성에 표현식으로 접근할 때 대괄호 표기법을 사용할 수 있다.
우선 각각의 사용되는 방법에 대해 예제로 알아보자.
let user1 = {
admin() {
alert("관리자 계정입니다.");
}
}
let user2 = {};
user1.admin?.(); // 관리자 계정입니다.
user2.admin?.();
해당 예제에서 사용한 것처럼 우선 admin 메서드의 존재 여부를 확인하고 나서 ()를 사용하여 함수를 사용하였다.
당연하게도 user2에서는 중간에 평가를 멈추었기 때문에 에러 없이 그냥 지나가게 된 것이다.
다음으로는 ?.[]의 예제를 살펴보자.
let user1 = {
firstName: "Violet"
};
let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다.
let key = "firstName";
alert( user1?.[key] ); // Violet
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
메서드 대상으로 옵셔널체이닝을 사용한 방법과 마찬가지로 우선 해당 객체가 존재하는지 확인한 후에 안전하게 프로퍼티를 읽을 수 있다.
읽기, 쓰기 그리고 삭제
읽기는 지금까지 봤던 예제를 통해 충분히 알아봤을 텐데,
그렇다면 과연 쓰기와 삭제는 가능한지에 대해 알아보자.
쓰기
let user = {};
user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment
에러가 발생하는 이유는 undefined = "Violet"이 되기 때문입니다.
그냥 부적절한 위치에서의 할당 행위를 에러로 보는 것이기 때문에 안된다고 알아두면 되겠다.
해당 에러는 여기에서만 발생하는 게 아니고, 다양한 상황에서도 발생하기 때문에 해당 에러에 대해 좀 더 자세히 알고 싶다면 아래 사이트를 통해 접해보는 것을 권장한다.
삭제
let user = {
name: 'HAN'
};
console.log(user) // { name: 'HAN' }
delete user?.name; // user가 존재하면 user.name을 삭제합니다
console.log(user) // {}
놀랍게도 삭제 역시 가능하다.
즉, 옵셔널 체이닝을 사용함에 있어 읽기와 쓰기에는 사용할 수 있지만 쓰기에는 사용할 수 없다는 의미이다.
참고: https://ko.javascript.info/optional-chaining
참고: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining
댓글