getStringLength 함수를 사용하는 모든 곳에서 undefined 일 가능성이 생긴다옵셔널 체이닝 공식 문서에 따르면
? 라는 키워드를 사용한다javascript 는 초기화 되지 않은 객체에 대해서 참조를 시도하거나 존재하지 않는 property 에 접근 할 경우 에러를 발생시킨다
이 에러는 전역으로 퍼지며 브라우저 화면을 하얗게 만들어버린다
const some = {
a: 1
}
some.a 에 접근 할 경우 1을 반환하며
some.b 에 접근 할 경우 에러를 ~ ! 반환하지 않는다
b 키에 접근하기 위한 some 이라는 객체가 undefined 가 아니기 때문이다
some.b.bb는 에러를 ~ ! 발생 시킨다 some 은 존재하나 some.b 는 undefined 이기때문에 undefined 에서 bb를 찾을 수 없기 때문이다
객체에 대한 검증 유틸을 만들어도 자바스크립트는 객체를 할당 받은 변수를 재할당이 가능도록 유지하기 때문에 객체가 깊어질수록 객체 전체에 대한 검증이 어려워 진다
data.a.aa 가 undefined 일 경우 data.b.bb 에 특정 값을 할당 해야한다.var data = ...
if (data.a.aa === undefined && data.b && data.b.bb) {
data.b.bb = 5;
}
객체를 사용 할 때마다 검증 케이스와 조건문을 만들어야 했으나 옵셔널 체이닝 등장 한 이유로 달라졌다!
es6에 비로써 재할당이 불가능한const키워드를 사용 할 수 있게 되었다
물론const키워드를 사용해도 객체는 재할당이 가능하다 ... 🫢
여기서는 객체를 불변 및 함수형 그리고 freeze 같은 개념으로 관리하는 상황을 배제한다
data?.b?.bb = 5;
정말 엄청난 기능이다. 에러를 신경쓸 필요도 없고 바로 검증 및 할당된다, ⚠️그러나 ⚠️ 아래와 같은 문제가 있다
아래와 같은 파라메터로 받아온 변수에 대한 길이를 리턴하는 함수 가 존재한다고 가정해보자
// parameter로 받아온 str 변수에 대한 길이를 리턴한다
const getStringLength = (str: string) => {
return str.length;
};
해당 함수는 parameter 로 str: string 타입을 받을 것이다
type Data = {
userInfo: {
user?: {
name?: string
}
}
}
const data: Data = {
userInfo: {
name: "some",
}
};
getStringLength(data.userInfo?.name)
실제 런타임에서는 문제 없이 동작하지만 타입스크립트는 붉은줄을 나타내며 에러가 날 것이다
개발자는 알 고 있다 data 의 unserInfo 의 name 은 undefined가 아니라는 것을 그러나 getStringLength 함수는 모른다. 실제로 Data 타입에서 name 이라는 키의 value는 string | undefined 일 가능성이 있기 때문이다
이때 많은 개발자들이 getStringLength 함수의 파라메터에 ? 혹은 string | undefined 를 넣는다..
- const getStringLength = (str: string) => {}
+ const getStringLength = (str: string | undefined) => {}
+ const getStringLength = (str?: string ) => {}
?와undefined둘다 타입에 대해서undefined가능성을 제시하지만 정확하게는optional과undefined에 대한 차이가 존재한다 만에 하나getStringLength를 변경하고자 한다면?보다는undefined를 사용하라
이유는 간단하다 data 변수는 서버에서 받아오는 값이기에 undefined 를 해제 할 수 없고 검증 로직을 하나 하나 만들기 귀찮고 어렵기때문이다
위와 같은 생각으로 getStringLength 함수의 파라메터의 타입을 변경하게된다
getStringLength 함수는 굉장히 심플하고 단일 기능을 위해 설계 했지만 본연의 목적을 잃어버리게 되었고
str 이 undefined 일 가능성이 생기면서 getStringLength 의 함수는 아래와 같이 변경 된다
// parameter로 받아온 str 변수에 대한 길이를 리턴한다
- const getStringLength = (str: string) => {
+ const getStringLength = (str: string | undefined) => {
+ return str.length;
- return str?.length;
};
number 타입이 return 되도록 추론된 getStringLength 함수는 이제 자동으로 number 와 undefined 를 리턴하게된다
getStringLength 함수를 사용하는 모든 곳에서 undefined 일 가능성이 생긴다이는 자연스럽게 getStringLength 함수를 사용하는 곳에서 옵셔널체이닝 사용을 강제하게되는 것과 마찬가지다
function NameLength() {
// 사용하기에는 편하다
const length = getStringLength(some.info?.data?.name);
// 강제로 someObj는 length 타입이 undefined
const someObj = {...someObj, length}
// 사용하기에는 편하다..
return <div>{someObj.length}</div>
}
위에서 사용된 NameLength 컴포넌트는 이제 특정 상황에따라 정말 부득이하게 빈공백 화면이 나타날 것이다
그러나 개발자들은 이를 예측하지 못하며 유저가 불만신고를 하지 않는 이상 계속해서 나타난다
그라파나, 센트리 어떤 앱도 이를 눈치 채지 못 할 것이다
왜냐면 옵셔널체이닝은 에러를 발생시키지 않기 때문이다..
우리가 타입스크립트를 사용한 이유가 무엇인가 ?
const name = "some";
// ⚠️ 빌드할때 터진다(옵션) (개발자가 눈으로 명확하게 에러를 인지 할 수 있다)
name = 123;
바로 휴먼에러를 방지 할 수 있다는 것이다.
개발자도 사람이고 실수는 한다. 그때 그 실수를 알려주는 것이 타입스크립트 라고 생각한다
문제는 본연의 목적을 상실한 옵셔널체이닝으로 타입스크립트가 주는 이점을 잃어버리게 된다
상황에 따른 대처 방법
- getStringLength(data.userInfo?.name)
// name이 `undefined` 일 가능성이 없다고 가정해야 한다.
+ getStringLength(data.userInfo?.name ?? "")
if (!data.userInfo?.name) {
// TODO sentry 로그 전송등 개발자에게 이 사실을 알리는 작업을 진행하자
}
- getStringLength(data.userInfo?.name)
// name이 `undefined` 일 가능성이 없다고 가정해야 한다.
+ getStringLength(data.userInfo.name ?? "")
if (!data.userInfo?.name) {
// 상황에따라 에러를 던져서 sentry 등이 캐치 하도록 유도하거나 여의치 않을 경우 return; 을 하도록하자
redirect(...)
}
- getStringLength(data.userInfo?.name)
// undefined 일 가능성은 존재 하지 않는다
+ getStringLength(data.userInfo.name)
- getStringLength(data.userInfo?.name)
// undefined 일 가능성은 존재 하지 않는다
+ getStringLength(data.userInfo.name!)
// or
+ getStringLength(data.userInfo.name as string)
getStringLength 함수에서 처리 할려면 최대한 조심스럽게 접근하자, 사용처를 명확하게 설정하자// parameter로 받아온 str 변수에 대한 길이를 리턴한다
- const getStringLength = (str: string) => {
+ const getStringLength = (str: string | undefined) => {
- return str.length;
+ return str.length ?? defaultLength;
};
상황에 따라 관리가 안될정도로 검증이 넘쳐 난다면 다양한 타입 검증 라이브러리를 고려하자