Optianal chaining 연산자
?.
는 각 체인의 참조가 유효한지 명시적으로 검증하지 않고, 연결된 체인 내에 깊숙이 위치한 속성 값을 읽을 수 있다.?.
연산자는.
체이닝 연산자와 유사하게 작동하지만, 만약 참조가 nullish (null 또는 undefined)이라면, 에러가 발생하는 것 대신에 표현식의 리턴 값은 undefined로 단락된다. 함수 호출에서 사용될 때, 만약 주어진 함수가 존재하지 않는다면, undefined를 리턴한다.
과연 이것은 무슨 말인가? 🤔
나는 1차 프로젝트를 시작하면서 부터 자주 마주하게 된 에러 메시지가 있는데 그 내용은 바로 다음과 같다.
다음과 같은 에러로 발을 동동 구를때 나를 구원해 준 것이 바로 이 ?.
였고 그 뒤로 ?.
는 마치 만능키처럼 "cannot read property" 로 시작해 "undefined"로 끝나는 대부분의 에러 메세지를 해결해주었다.
optional chaining은 ES2020에 채택된 아주 최신의 문법인데, 몇 가지 사례를 통해 optianal chaining의 등장 배경을 알아 보도록 하자
사용자가 여러 명 있는데 그중 몇 명은 주소 정보를 가지고 있지 않다고 가정해보자. 이럴 때 user.address.street
를 사용해 주소 정보에 접근하면 에러가 발생할 수 있다.
let user = {}; // 주소 정보가 없는 사용자
alert(user.address.street); // TypeError: Cannot read property 'street' of undefined
optional chaining이 공식채택 되기 전에는 이러한 문제 해결을 위해서 &&
연산자를 사용했다.
let user = {}; // 주소 정보가 없는 사용자
alert( user && user.address && user.address.street ); // undefined, 에러가 발생하지 않습니다.
중첩 객체의 특정 프로퍼티에 접근하기 위해 거쳐야 할 구성요소들을 AND로 연결해 실제 해당 객체나 프로퍼티가 있는지 확인하는 방법을 사용했다. 그런데 이렇게 AND를 연결해서 사용하면 코드가 아주 길어진다는 단점이 있었다.
?.
은 ?.
'앞’의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환한다.
즉, null이나 undefined인 대상에 대해서 property를 찾으려 하지 않고 평가를 중단하기 때문에, "can not read..." 와 같은 에러메세지를 만나지 않을 수 있는 것이다.
이것이 바로 만능키의 비밀!
그렇다면 Optional chaining을 사용해 user.address.street
에 안전하게 접근해보자.
let user = {}; // 주소 정보가 없는 사용자
alert( user?.address?.street ); // undefined, 에러가 발생하지 않는다.
user?.address
로 주소를 읽으면 아래와 같이 user 객체가 존재하지 않더라도 에러가 발생하지 않는다.
let user = null;
alert( user?.address ); // undefined
alert( user?.address.street ); // undefined
그러나 유의할 점 중 하나는 위 예시를 통해 우리는 ?.은 ?. ‘앞’ 평가 대상에만 동작되고, 확장은 되지 않는다는 사실을 알 수 있다.
바로 이 부분이 내가 Optional chaining에 대해서 글을 쓰게 된 계기이기도 하다. 여느때와 같이 만능키(optional chaining)을 사용하던 중 다음과 같은 코드 리뷰를 받았다.
그리고 이 질문에 대한 답변은 다음과 같다
반드시 존재해야 하는 대상에
?.
를 사용한다면 실수로 값을 할당하지 않았을 때 에러를 바로 발견하지 못할 수 있어 디버깅에 어려움이 있을 수 있다.
또한 ES2020, 최신의 문법으로 Node 버전이 낮을 경우 사용이 불가하니 유의하도록 하자!
?.
는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춘다. 참고로 이런 평가 방법을 단락 평가(short-circuit)라고 부른다.
왼쪽의 평가 대상에 값이 없을 경우 즉시 평가를 멈춘다는 것이 핵심인데, 따라서 함수 호출을 비롯한 ?.
오른쪽에 있는 부가 동작은 ?.
의 평가가 멈췄을 때 더는 일어나지 않는다.
이러한 성질을 이용하면 굉장히 효율적인 코드 구현이 가능하다.
let user = null;
let x = 0;
user?.sayHi(x++); // 아무 일도 일어나지 않는다
alert(x); // 0, x는 증가하지 않는다.
?.
은 연산자가 아니다. ?.
은 함수나 대괄호와 함께 동작하는 특별한 문법 구조체(syntax construct)이다.
함수 관련 예시와 함께 존재 여부가 확실치 않은 함수를 호출할 때 ?.()
를 어떻게 쓸 수 있는지 알아보자.
한 객체엔 메서드 admin이 있지만 다른 객체엔 없는 상황이다.
두 상황 모두에서 user 객체는 존재하기 때문에 admin 프로퍼티는 .만 사용해 접근했습니다.
let user1 = {
admin() {
alert("관리자 계정입니다.");
}
}
let user2 = {};
user1.admin?.(); // 관리자 계정입니다.
user2.admin?.();
그리고 난 후 ?.()
를 사용해 admin의 존재 여부를 확인했다. user1엔 admin이 정의되어 있기 때문에 메서드가 제대로 호출되었다. 반면 user2엔 admin이 정의되어 있지 않았음에도 불구하고 메서드를 호출하면 에러 없이 그냥 평가가 멈추는 것을 확인할 수 있다.
.대신 대괄호 []를 사용해 객체 프로퍼티에 접근하는 경우엔 ?.[]를 사용할 수도 있다. 위 예시와 마찬가지로 ?.[]를 사용하면 프로퍼티 존재 여부가 확실치 않은 경우에도 안전하게 프로퍼티를 읽을 수 있다.
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
?.
은 delete
와 조합해서 사용할 수도 있다.
delete user?.name; // user가 존재하면 user.name을 삭제합니다.
그러나
?.
은 읽기나 삭제하기에는 사용할 수 있지만 쓰기에는 사용할 수 없다.
?.은 할당 연산자 왼쪽에서 사용할 수 없다.
// user가 존재할 경우 user.name에 값을 쓰려는 의도로 아래와 같이 코드를 작성해 보았습니다.
user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment
// 에러가 발생하는 이유는 undefined = "Violet"이 되기 때문입니다.