양날의 검, 옵셔널 체이닝

JS에서 ?는 악마의 유혹이 아닐까

Optional Chaining은 자바스크립트에서 객체에 접근하는 방법 중 하나입니다. 간결하고, 안전하죠.

:::info Optional Chaining이란?

?.은 ?.'앞’의 평가 대상이 Nullish하면, 즉 undefined나 null이면 평가를 멈추고 undefined를 반환합니다.

:::

https://lnwblzacktgzeiihvxtu.supabase.co/storage/v1/object/public/contents/prod/1.png-73603

이 문법은 편리하고 유용하지만, 단점이 분명히 존재합니다. 자세히 알아보고 잘 쓰도록 해요!

현대 웹앱 서비스에서 데이터는 흔히 JSON 형태로 관리합니다.

const developer_hj = {
    name : "hj",
    age: 29,
    skills: ["javaScript", "react", "typeScript", "next.js"],
    company: {
        name: "hj_company",
        category: "dev"
    }
    ...
}

자바스크립트로 이를 핸들링할 때 흔히 dot notation이나 bracket notation을 활용합니다.

///1. dot notation
const hisName = developer_hj.name;
///2. bracket notation
const hisName = developer_hj['name'];

하지만 데이터가 항상 예쁘게 내려온다고 믿을 순 없지 않습니까. 에러 핸들링을 추가해야합니다. 다음과 같이 실제로는 존재하지 않는 키로 데이터를 조회했을 때의 결과를 확인해봅시다.

/// 오류가 발생하지 않습니다. undefined를 반환합니다.
const hisMoney = developer_hj.money;

/// 오류가 발생합니다.
const histHouseValue = developer_hj.house.value;

왜 둘 다 없는 데이터인데, 첫번째 예시에서는 오류가 발생하지 않고, 두번째에서는 오류가 발생할까요?

이는 자바스크립트의 프로토타입 체이닝과 값 평가 방식과 관련되어 있습니다.

javaScript에서 객체를 조회할 땐 왼쪽부터 평가된 값을 반환합니다.

첫번째 예시에서 hisMoney를 평가할 땐 developer_hj에 money라는 key가 존재하지 않으니 undefined로 평가하고 그 값을 반환합니다.

두번째 예시에서 developer_hj.house가 undefined로 평가됩니다. 그렇게 반환된 undefined라는 값에서 value라는 key로 값을 조회하니 오류가 발생한 것입니다.

:::info 프로토타입 체이닝

자바스크립트에서는 Primitive Type객체가 아닌 Reference Type은 ProtoType Chaining을 가집니다. 객체를 찾기 위해 상위 프로토타입 체인을 찾아요.

가령 Array 타입 데이터에서 .map 메소드를 사용할 수 있는건, 배열 객체의 최상위 프로토타입에 해당 메소드가 이미 정의되어 있기 때문입니다.

다만 조회하다가 상위 객체가 nullish하다면 조회할 수 있는 프로토타입이 없음에도 조회하게 되므로 undefined를 반환하지 않고 오류가 발생합니다.

:::

이렇게 중첩된 객체에서 존재하지 않는 특정 property를 조회할 때 오류 없이 항상 undefined를 반환하고 넘어가게 해주는 문법이 optional chaining입니다.

사용 방법은 다음과 같아요.

// MDN 문서에 나온 예시를 그대로 차용했습니다.

// dot notation
obj?.prop;

// bracket notation
obj?.[expr];

// array
arr?.[index];

//function
func?.(args);

그럼 이 사용법으로 위에서 제시했던 예시를 오류가 발생하지 않도록 바꿔볼까요?

/// 오류가 발생하지 않고, undefined가 반환됩니다.
const histHouseValue = developer_hj.house?.value;

undefined로 평가되는 값은 오류가 아닙니다. 어떤 값이 정상적으로 반환된 것이죠. 사용자 경험이 떨어질 순 있어도 런타임에 서비스 자체를 다운시킬 수도 있는 치명적인 문법 오류보다는 훨씬 낫기 때문에, 객체 조회할 때 무지성으로 물음표를 달아놓는 경우가 많아요(물음표 살인마..?)

하지만 이 문법은 양날의 검입니다.

Optional Chaning의 목적은 자바스크립트가 에러를 던지지 않게 함입니다. 결국 에러 대신 undefined를 반환하고, 우리가 기대했던 동작을 하지 않습니다. 즉, 런타임에 오류가 아닌 오동작을 하게됩니다.

const [userName, setUserName] = useState<string>('');

useEffect(() => {
	setUserName(data.user?.name);
}, []);

/// 이름이 렌더링되어야 할 자리에 undefined가 나올 수 있다.
return <p>{userName}</p>;

Optional Chaining은 에러를 뱉지 않습니다. 그저 평가 결과에 따라 그 값을 반환하거나 undefined를 반환하는 함수일 뿐입니다. 그래서 에러가 아니에요. 즉, try-catch를 통한 디버깅이 불가능합니다. 가령, 다음과 같은 처리에서 데이터가 얼마나 망가져있던, 에러처리는 불가능합니다. 에러 기록도 못하고, 결국 오동작을 추적할 수 없게합니다.

const data = { a: 1 };
try {
	console.log(data.asdf?.asdfasdf);
} catch (error) {
	// 에러처리 불가. try 문은 정상실행된다.
}

다음과 같이 특정 데이터가 없음에 대한 처리를 분명히 해줘야 하죠.

const isData = (data: unknown): data is Data => {
	return typeof (data as Data).id === 'number';
};

try {
	if (isData(data)) {
		console.log(data.id);
	}
} catch (error) {
	//에러처리
}

만약 try-catch로 에러가 전파되는 과정이 10단계라고 가정해봅시다. 한 3번째 단계 쯤에서 optional chaining을 걸어놓는 바람에 오류가 발생하지 않아 다음에 호출될 함수부터 모두 에러가 발생했다고 가정해볼게요. 전파된 에러를 추적하는데 큰 애를 먹겠죠?

지원하기 시작한지 몇년 안되는 연산자라서 구버전 브라우져를 위한 폴리필이 필요합니다.

https://lnwblzacktgzeiihvxtu.supabase.co/storage/v1/object/public/contents/prod/2.png-61519

// 1. temp에 상위 객체를 호출
let temp = obj.first;
// 2. 상위 객체가 nullish하면 undefined를, 그렇지 않으면 다음 프로퍼티에 접근하여 반환
let nestedProp = temp === null || temp === undefined ? undefined : temp.second;

정말 유용하고 간편해서 저도 연산자로 현업에서 정말 자주 씁니다.

다만 분명한 문제가 있으므로

왼쪽 평가대상이 없어도 괜찮은 경우에만 선택적으로 사용

MDN - Optional Chaining

javascript.info - Optional Chaining

Copyright © HOJUN IN. All rights reserved