noUncheckedIndexedAccess는 왜 strict 모드의 기본값이 아닐까?
-
tsconfig의 compilerOptions에서 true 혹은 false로 설정할 수 있는 noUncheckedIndexedAccess 옵션은 객체나 배열에 접근할 때 undefined 가능성을 더해주어 더 안전한 코드를 작성하도록 도와줍니다.
-
안정성을 높여줄 수 있는 옵션임에도 불구하고 tsconfig에서 strict를 true로 설정하더라도 noUncheckedIndexedAccess의 기본값은 false입니다.

TypeError
위와 같은 에러를 보신 적이 있으신가요? 아시다시피 이 에러는 객체가 undefined임에도 불구하고 그 객체의 속성에 접근할 때 발생합니다. 꽤나 자주 겪어보았던 터라 이 에러가 뜨면 오히려 안심하고 디버깅을 하곤 합니다.
근데 이번엔 에러가 났던 코드를 보고 한 가지 의문점이 생겼습니다. 에러를 발생시키고 있던 문제의 코드는 다음과 같은데요,
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useCustomHook() {
const { data: enrolleds = [] } = useSuspenseQuery(enrolledsQuery);
...
return {
...
// enrolleds가 빈 배열일 경우, enrolleds[0]은 undefined가 됨.
// 따라서 undefined.course와 같이 undefined의 속성에 접근하려고 하면 에러 발생.
courseName: enrolleds[0].course.name,
...
}
}
여기서 enrolleds[0]은 undefined일 수 있는데 왜 TypeScript에서는 type error를 IDE 상에서 발생시켜주지 않았을까?라는 것입니다.
저의 이러한 의문과 착각은 엄격한 타입 체킹을 해주는 strict 옵션이 모든 걸 커버해주겠지?라는 믿음 때문이었습니다. 실제로 다음 사진과 같이, strict를 true로 켜놓았다고 해서 type error가 표시되진 않습니다.

strict만 true일 때
알아보니 배열이나 객체에서 undefined일 수 있는 속성에 접근할 때 type error를 발생시키기 위해서는 strict 옵션과 함께 noUncheckedIndexedAccess라는 옵션 또한 켜주어야 합니다.
// tsconfig.json
{
"compilerOptions": {
...
"strict": true,
"noUncheckedIndexedAccess": true,
...
},
...
}
이렇게 설정해주어야 비로소 다음과 같이 type error가 IDE 상에 표시되는 걸 확인할 수 있습니다.

strict만 true일 때
왜 strict를 켜도 noUncheckedIndexedAccess를 따로 켜주어야 할까?
타입을 엄격히 체크해주는 strict를 켜도 왜 noUncheckedIndexedAccess는 따로 또 켜주어야 할까요? strict만 true로 설정해두면 noUncheckedIndexedAccess 또한 기본적으로 true가 되어 조금 더 안전한 코드를 작성할 수 있게 해주면 당연히 좋지 않을까요?
라는 의문이 들어서 한 번 이와 관련된 글들을 찾아보았습니다. 반갑게도 같은 의문을 제기한 글을 깃헙 이슈에서 발견할 수 있었어요.
요약하자면 다음과 같습니다.
- 이슈 작성자는 strict가 true인 상태에서 다음과 같은 코드는 타입 에러를 발생시키지 않는다며, strict가 true일 때 noUncheckedIndexedAccess 또한 자동으로 true가 되어야 한다고 주장함.
const numbers: Array<number> = [];
// 여기서 test 타입은 number가 아니라 number | undefined여야 한다고 주장.
const test = numbers[1];
- 마이크로소프트의 타입스크립트 개발팀 리드는 다음과 같이 주장하며 지금처럼 별도의 옵션으로 존재하는 것이 낫다는 의견을 내비침.
- strict 모드에 noUnchechedIndexedAccess가 기본적으로 켜지도록 변경하면 기존 코드베이스에 많은 false positive들을 발생시킬 것
- 특히 C-style loop에서 많은 오류를 표시할 것
여기서 false positive란, 실제론 문제가 없음에도 문제가 있다고 표시되는 경우를 말합니다. 그리고 C-style loop란 명령형 for문을 말하는데요, 이 개발팀 리드가 주장하는 false positive의 경우는 대표적으로 다음과 같습니다.
const array = [1, 2, 3];
for (let i = 0; i < array.length; i++) {
const element = array[i];
element.toFixed(2); // 'element' is possibly 'undefined'라는 type 에러 발생.
}
위 코드에서는 length를 확인해서 loop를 돌기 때문에 element는 undefined가 될 수 없습니다. 그럼에도 불구하고 noUncheckedIndexedAccess를 켜두면 type error가 발생하게 됩니다. 실제론 문제가 없음에도 문제가 발생한, 즉 false positive인 C-style loop 코드입니다.
이러한 코드는 흔하게 쓰입니다. 따라서 갑자기 strict 모드에 noUnchechedIndexedAccess를 기본적으로 켜지게 변경하면, TypeScript 버전을 올렸을 경우 갑작스런 type error가 많이 발생할 것이란 우려를 내비친 겁니다.
어쨌든 에러가 날 수 있는 여지를 최대한 개발 과정에서 막기 위해 noUncheckedIndexedAccess 옵션을 true로 설정해주었습니다. C-style loop 코드가 거의 없기도 하고, 프로덕션 환경에서 일어날 수 있는 에러를 사전에 막는 것이 가장 우선이다라고 판단했기 때문입니다.
TypeScript 개발장의 의도를 직접적으로 알 수 있었어서 의문을 쉽게 해소할 수 있었던 재밌는 경험이었네요! 그리고 tsconfig 옵션이나 eslint 룰을 팀에 맞게 다시 한 번 잘 정비하여 더 많은 에러를 사전에 방지해봐야겠다는 생각도 들었습니다.