
Codefug Blog

JS를 완벽하게 하기 위한 여정
프론트엔드 환경에서 TypeScript는 JavaScript보다 더 많이 사용됩니다.
단순히 사용하는 것을 넘어 TypeScript가 해결하는 문제를 이해해야 합니다.
JavaScript는 유연성이 높지만 유지보수 측면에서 문제가 됩니다.
| 변수 선언 방법 | 스코프 범위 | 재할당 가능 여부 | hoisting 여부 | 중복 선언 가능 여부 |
|---|---|---|---|---|
| var | 함수 스코프 | 가능 | o | 가능 |
| let | 블록 스코프 | 가능 | x (ReferenceError) | 불가능 (Syntax Error) |
| const | 블록 스코프 | 불가능 | x (ReferenceError) | 불가능 (Syntax Error) |
var는 예전 코드나 바벨로 변환된 코드에서 자주 보입니다.
JavaScript에서는 코드 실행 중 변수 타입이 int에서 float, string 등으로 자유롭게 변할 수 있습니다.
정적 타입 언어(C, Java, Kotlin, C# 등)는 타입을 먼저 지정하고 다른 타입 할당 시 오류를 냅니다.
작은 프로젝트에서는 JavaScript의 자유로움이 편할 수 있습니다.
하지만 코드 규모가 커지고 확장이나 수정이 필요해지면 정적 타입의 엄격함이 필요합니다.
JavaScript는 this 문제를 개선하기 위해 화살표 함수를 도입했습니다.
화살표 함수는 함수 선언문이 아니므로 호이스팅을 방지합니다.
이런 개선에도 불구하고 JavaScript의 과도한 자유로움은 안정성 측면에서 단점입니다.
JavaScript 엔진은 데이터가 할당되지 않은 변수를 undefined로 자동 설정합니다.
null은 개발자가 명시하는 빈 값이고, undefined는 엔진이 자동으로 설정하는 빈 값입니다.
하지만 개발자가 undefined를 명시해도 오류가 발생하지 않습니다.
작성 시점에는 편할 수 있지만 유지보수 시 디버깅이 어려워집니다.
실행 전에 코드를 검사할 수 있다면 문제가 되지 않습니다.
핵심 문제는 JavaScript가 과도한 자유로움을 허용하면서도 검사 장치가 부족하다는 점입니다.
TypeScript는 이 문제를 해결하기 위해 등장했습니다.
TypeScript는 JavaScript 문법을 그대로 사용하면서 정적 타입을 추가하고 검사합니다.
TS > tsc (컴파일러)로 변환 > JS > JS 실행 (node 환경)
tsc는 두 가지 역할을 합니다.
첫 번째는 TypeScript를 JavaScript로 변환하는 것입니다.
두 번째는 타입을 검사하는 것입니다.
타입 검사를 통과하지 못해도 JavaScript로 변환은 됩니다.
참고TypeScript를 직접 실행할 수 있는 Deno, Bun 같은 런타임도 있지만 아직 대중화되지 않았습니다.
정적 타입 언어를 사용하는 백엔드는 응답 데이터의 타입이 명확합니다.
{
"totalCount": 0,
"list": [
{
"updatedAt": "2024-06-05T04:30:04.759Z",
"createdAt": "2024-06-05T04:30:04.759Z",
"likeCount": 0,
"writer": {
"nickname": "닉네임",
"id": 1
},
"image": "string",
"content": "게시글 내용입니다.",
"title": "게시글 제목입니다.",
"id": 1
}
]
}
JavaScript에서는 const로 객체를 선언해도 내부 프로퍼티 변경이 가능합니다.
타입 변경도 자유롭게 됩니다.
const Article = {
totalCount: 0,
list: [
{
updatedAt: "2024-06-05T04:30:04.759Z",
createdAt: "2024-06-05T04:30:04.759Z",
likeCount: 0,
writer: {
nickname: "닉네임",
id: 1,
},
image: "string",
content: "게시글 내용입니다.",
title: "게시글 제목입니다.",
id: 1,
},
],
};
//...
Article.id = "수정이지롱";
//...
console.log(Article.id); // '수정이지롱'으로 number에서 string으로 자료형 변경
코드가 짧으면 이런 실수를 피할 수 있지만 코드가 길어지면 휴먼 에러가 발생합니다.
TypeScript는 타입을 지정하여 이 문제를 방지합니다.
fetch로 받아온 데이터의 타입을 미리 정의합니다.
type Article = {
id: number;
title: string;
content: string;
image: string | null;
likeCount: number;
createdAt: string;
updatedAt: string;
writer: {
id: number;
nickname: string;
};
};
const article: Article = {
updatedAt: "2024-06-05T04:30:04.759Z",
createdAt: "2024-06-05T04:30:04.759Z",
likeCount: 0,
writer: {
nickname: "닉네임",
id: 1,
},
image: "string",
content: "게시글 내용입니다.",
title: "게시글 제목입니다.",
id: 1,
};
article.id = "수정이지롱";
// Type 'string' is not assignable to type 'number'.
TypeScript는 id가 number 타입인데 string을 할당했다는 에러를 표시합니다.
const [images, setImages] = useState<string[]>([]);
// ...
const handleFileSelect = () => {
if (fileRef?.current?.files == null) {
return;
}
const newFile = Array.from(fileRef.current.files).map((file) =>
URL.createObjectURL(file),
);
setImages((prevImages) => [...prevImages, newFile]);
};
위 코드는 input type="file"을 useRef로 참조하여 파일을 받습니다.
URL.createObjectURL(file)로 FakePath를 실제 URL로 변환합니다.
변환된 URL로 미리보기를 보여줍니다.
setImages에는 spread 연산자로 prevImages와 새로운 파일을 넣습니다.
문제점은 newFile도 spread로 분해해야 한다는 점입니다.
JavaScript 환경이었다면 오류 없이 넘어갔을 것입니다.
TypeScript는 명확한 오류를 보여줍니다.
Argument of type '(prevImages: string[]) => (string | string[])[]' is not assignable to parameter of type
'SetStateAction<string[]>'.Type '(prevImages: string[]) => (string | string[])[]' is not assignable to type '(prevState: string[])=> string[]'.
Type '(string | string[])[]' is not assignable to type 'string[]'.
Type 'string | string[]' is not assignable to type 'string'.
Type 'string[]' is not assignable to type 'string'.
오류 메시지가 길지만 내용은 간단합니다.
(string | string[])[] 타입을 넣었지만 setStateAction<string[]>와 맞지 않습니다.
(string[])[] 부분이 잘못되었음을 알 수 있습니다.
const handleFileSelect = () => {
if (fileRef?.current?.files == null) {
return;
}
const newFile = Array.from(fileRef.current.files).map((v) =>
URL.createObjectURL(v),
);
setImages((prevImages) => [...prevImages, ...newFile]);
};
위와 같이 수정하면 해결할 수 있습니다.
TypeScript를 사용하면서 문법적 자유로움이 항상 좋은 것은 아님을 알았습니다.
타입 지정, 인터페이스 상속, Key 타입 설정 등은 초반에 시간이 걸립니다.
하지만 한번 타입을 지정하면 휴먼 에러를 미리 방지할 수 있습니다.
JavaScript 생태계는 TypeScript를 통해 스크립트 언어의 단점을 상당 부분 극복했습니다.
잘못된 정보가 있거나 추가할 내용이 있다면 댓글로 알려주세요.
감사합니다.