타입스크립트 쓰는 이유 / 필수 문법
자바스크립트(JS)의 업그레이드 버전으로 개발자 경험 개선과 코드 품질 향상을 위한 도구이다. (JavaScript + Type문법)
에러 메시지나 타입 오류는 런타임이 아닌 개발 시점(에디터/터미널)에서만 동작한다.
JS는 Dynamic Typing이 가능하기 때문에 숫자와 문자의 연산이라도 JS가 자동으로 문자를 숫자로 바꿔서 연산해준다. 그런데 이러한 자유도와 유연성은 프로젝트가 커질수록 악영향을 끼친다. 만약 남이 짠 코드에 어떤 type과 관련된 버그가 있다면? 이때 TS는 타입을 엄격히 검사함으로써 이러한 시도조차 하지 못하게 막아준다.
에러메시지의 수준이 높다. 에러메시지의 내용이 추상적이고 추적이 어려운 JS와 달리, TS는 타입 오류 및 오타 체크까지 도와준다.
즉 문법은 JS와 동일하나, 엄격한 타입 검사 기능이 추가됐다고 볼 수 있다.
TS 사용 목적
- 정적 타입 검사 (컴파일 타임에서 오류 감지)
- 타인의 코드 구조와 의도를 쉽게 파악
- 대규모 프로젝트에서의 유지보수성 확보(코드의 계약을 문서화)
- 런타임 에러 중 상당수를 사전에 방지
참고로, 런타임에서 TypeScript는 JavaScript로 변환되어 실행된다. 타입 정보는 모두 제거되고, 브라우저 또는 node.js에서 타입은 전혀 사용되지 않는다. 결국 TS는 보안 도구가 아닌 개발 도구라고 할 수 있다.
기본 환경 세팅 (React 등을 쓰지 않는 기본 환경 기준)
1. node.js 최신 버전 설치
2. 터미널에서 typescript 설치 (npm install -g typescript)
3. tsconfig.json 생성 (js 컴파일 시 설정)
4. ts 파일을 만든 후 터미널에 'tsc -w' 실행
5. 즐겁게 코딩하기
필수 문법
(1) 변수에 타입 설정
let name: string = "kim";
이후 이 name 변수에 숫자 등을 할당하려고 하면 에러가 표시됨. 명시할 수 있는 타입 목록에는 'string, number, boolean, null, undefined, bigint, [ ], { }' 등이 있다.
(2) array에 타입 설정
let name: string[] = ["kim", "james"];
(3) object 타입 설정
let person: { name: string; age: number; email?: string } = {
name: "kim",
age: 27,
};
만약 특정 속성이 들어오는 것이 필수가 아니라면, 컬럼명 뒤에 '?'를 붙여 해당 속성-값이 object에 없더라도 에러를 표시하지 않게 할 수 있다.
(4) 다양한 타입이 들어오게 설정 (Union Type)
'|' 기호를 이용해 설정한다.
let name: string | number = 9;
이를 이용해서 커스텀 타입 또한 만들 수 있다.
type mytype = string | number;
let name: mytype = 9;
(5) 함수 파라미터, 반환값에 타입 설정
파라미터마다 ( ) 안에 타입을 지정할 수 있고, 반환값에 대해서는 ( ) 와 { } 사이 : 뒤에 명시해 타입을 지정할 수 있다.
// 파라미터로 number, return값으로 number
function func(x: number): number {
return x * 2;
}
func('james') // 에러
func(12) // 정상 동작
- 반환값 타입에 void를 명시하면, 실수로 뭔가를 return하는 것을 사전에 막을 수 있다.
- 타입이 지정된 파라미터는 필수적으로 전달해야 한다.
- 만약 파라미터가 옵션일 경우, 변수명 뒤에 '?'를 붙여 받아도 되고, 안받아도 되게 할 수 있다.
- ?를 붙이는 것은 ' | undefined' 를 뒤에 붙이는 것과 같은 의미
- 입력받지 못한 파라미터는 자동으로 undefined가 되기 때문에 '?'가 성립한다.
(6) array에 쓸 수 있는 커스텀 타입 설정
// array에서 첫번째 값은 number, 두번째 값은 boolean이어야 한다.
type Member = [number, boolean];
let john:Member = [3, false]
(7) object의 모든 속성에 쓸 수 있는 커스텀 타입 설정
// 모든 속성이 같은 타입의 값을 가져야 할 때
type Member = {
[key: string]: string;
};
let customer: Member = {
name: "john",
age: "29",
};
(8) Class에서 타입 설정
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
타입스크립트 기본 타입 정리 (primitive types)
TS에서는 변수에 타입을 지정할 수 있는데, 이는 해당 변수의 타입을 보장하도록 방어막을 세우는 것이라 할 수 있다. (타입실드)
기초적인 타입 설정 방법은 위에 이미 적었으므로 생략한다.
자동 타입 지정
타입 지정 문법은 사실 할당 시의 값에 따라 TS가 자동으로 타입을 지정해 주기 때문에, 생략이 가능하다.
// 자동으로 string 타입 지정
let people = 'kim';
단, 선언만 하고 값을 할당하지 않은 경우 자동으로 any 타입이 된다. (타입실드 해제)
타입을 미리 정하기 애매할 때 (union, any, unknown)
Union Type
타입을 2개 이상 합친 새로운 타입. '|' 를 사용하여 여러 타입을 엮는다.
let person: number | string = 'temp';
person = 'jkh'; // 가능
person = 3; // 가능
주의할 점으로, 연산 불가능한 타입이 엮여 있으면 연산이 불가능해진다.
숫자 or 문자가 모두 가능한 array, object 타입 지정
let person: (string | number)[] = [12, "jkh", 34];
let obj: {
a: string | number;
} = { a: "jkh" };
obj.a = 34;
Any Type
모든 자료형을 허용한다. 다만 이는 TS의 사용 의미를 무색하게 만드므로, 타입실드를 해제하는 문법으로 쓰인다. 다만 타입실드를 해제했기에 타입 관련 버그들을 추적할 수 없게 된다.
let name: any;
name = 123;
name = true;
let var1: string = name; // 정상 작동 (다른 변수 타입실드도 해제)
Unknown Type
any와 같이 모든 자료형을 허용한다. 다만 any보다 훨씬 안전한데, 다른 변수의 타입실드에 영향을 끼치지 않는다. 또한 연산은 'any', 'number', 'bigint'만 가능하도록 TS가 엄격하게 규칙을 정해놓았기 때문에, 이를 활용한 연산 또한 막을 수 있다.
let name: unknown;
name = 123;
name = true;
let var1: string = name; // 오류
타입 확정하기 (Narrowing & Assertion)
Narrowing
'number | string' 같이 Type이 아직 하나로 확정되지 않았을 경우, Type Narrowing을 써야 한다. 이는 TS에서 꼭 지켜야 할 코딩 방식 중 하나.
'typeof 변수' 연산자를 사용하면 해당 변수의 Type을 문자열로 반환받을 수 있게 되는데 이를 통해 Type을 하나로 거르는 Type Narrowing이 가능해진다.
function func(x: number | string) {
if (typeof x === "string") {
return x + "1";
} else {
return x + 1;
}
}
만약 이를 위해 if문을 썼다면, else, else if 등을 끝까지 써줘야 안전하다.
또한 Narrowing으로 판정해주는 문법들에는 'typeof 변수'뿐만 아니라, '속성명 in 오브젝트자료', '인스턴스 instanceof 부모' 등이 있다. 그리고 Array의 경우 typeof로 array인지를 알 수 없고, 'Array.isarray(변수)'가 true/false인지로 판단해야 한다.
Assertion
Type 덮어쓰기. '변수 as Type'으로 사용하며, 이 경우 컴파일러가 변수를 해당 Type으로 인지하게 된다. 다만 사용법에 있어서는 주의해야 한다.
function func(x: number | string) {
let array: number[] = [];
array[0] = x as number;
}
Assertion 문법의 용도
1. Narrowing
- 덮어쓰기라고는 하지만, Type을 a 에서 b 로 변경할 수는 없다.
2. 무슨 Type이 들어올지 100% 확실할 때
- Assertion 문법을 사용하면 더이상 TypeError를 잡아낼 수 없게 된다.
- 이러면 굳이 쓸 이유가 없어진다.
- 때문에 대부분의 상황에서는 if문을 사용한다.
- 남이 짠 코드 수정 시 왜 TypeError가 나는지 모를 때 등에 비상용으로 사용한다.
타입 변수에 담아쓰기 (type 키워드, readonly)
type 변수
타입이 길고 복잡할 때, 이를 변수에 담아서 쓸 수도 있다.
type AnimalType = string | number | undefined;
let animal: AnimalType;
type AnimalType = string; // 오류
이렇게 type 키워드로 타입 변수를 만드는 것을 type alias라고 한다. 같은 이름의 type 변수는 재정의가 불가능하다.
또한 기존 primitive 타입 말고도 object 타입 또한 변수에 담을 수 있다.
type AnimalType = { name: string; age: number };
let animal: AnimalType = { name: "kim", age: 20 };
이를 통해 가독성 좋은 코드를 만들 수 있다.
- type 변수 작명 관습
1. 영어 대문자로 시작한다.
2. 변수명 뒤에 'Type'을 붙인다. (옵션)
참고로, type alias로 만든 타입끼리도 union type으로 합치기가 가능하다.
type Name = string;
type Age = number;
type Person = Name | Age;
또한 '&' 연산자로 object 타입을 extend할 수도 있다. 다시말해 여러 타입의 object 속성-값 쌍을 합친 하나의 object 타입을 만들 수 있다.
type PositionX = { x: number };
type PositionY = { y: number };
type NewType = PositionX & PositionY;
let x: NewType = { x: 12, y: 30 };
readonly
기존 변수 선언에 사용되는 const는 변수 재할당을 막아주지만, object 자료형 수정까지는 막지는 못한다. 그런데 이때 type 명시 중 특정 속성 왼쪽에 readonly를 추가하면 object 자료형의 수정도 막을 수 있다.
type Girlfriend = {
readonly name: string;
};
const girl: Girlfriend = {
name: "julie",
};
girl.name = "maria"; // 불가
단, TS error는 에디터 및 터미널에서만 존재할 뿐, 실제 변환된 js 파일에서는 에러 없이 동작한다. (변경된다)
Literal Types로 그럴듯한 const 변수 만들기
Literal Types
타입 지정뿐만 아니라, 더 엄격하게 지정해 놓은 값만 들어오게도 만들 수 있는데 이를 Literal Types라고 한다.
let name: "kim" | "lee";
name = "kim"; // 정상
name = "lee"; // 정상
name = "kin"; // 오류
이를 통해 변수에 뭐가 들어올지 더 엄격하게 관리가 가능하다. 그리고 Alt + Enter 등으로 자동완성 추천 목록을 띄우면 명시된 해당 값이 뜨기 때문에 자동 완성 또한 지원한다.
함수 파라미터, 반환값 또한 Literal Types를 지정할 수 있다. 따라서 함수 파라미터에 들어올 값 또는 반환값이 얼마 안된다면 유용하게 활용 가능하다.
function func(a: 'hello'): (1 | 0) {
return 1;
}
func('hello');
Literal Types로 const 변수의 한계 보완하기
const 변수는 재할당을 금지하지만, object 자료형의 경우 안의 값을 수정할 수 있다는 한계가 있다. 이때 literal types을 활용하면 이를 보완할 수 있는데, 변하지 않는 정보를 넘어서, 가끔은 변하는 중요한 정보를 저장하고 싶을 때 literal types를 활용한다.
- Literal Types의 한계
- 함수 파라미터에 literal types로 특정 값을 명시한다면, 해당 타입이 아닌 해당 자료(값 그자체)만 들어올 수 있게 되므로, 만약 다른 object 자료 안의 특정 속성 값이 해당 값이라고 하더라도 이를 활용할 수 없다.(타입은 같지만 값 그자체가 아니기 때문에)
- 해결법1: object 만들 때 타입지정을 확실히 하기
- 해결법2: as 문법으로 타입 덮어쓰기
- 해결법3: as const를 자료형 옆에 붙이기 (완전히 잠그기)
- object의 value 값을 그대로 타입으로 지정한다.
- object의 모든 속성들에 readonly를 붙여준다.
function myfunc(a: "kim") {}
// 방법1 (object에 자료형 명시)
var data: { name: "kim" } = {
name: "kim",
};
// 방법3 (as const 사용)
var data = {
name: "kim",
} as const;
// 방법2 (as 문법)
myfunc(data.name as "kim");
함수와 메서드에 type alias 지정하기
함수에 type alias 지정
함수 전체 형식을 type에 넣고 사용할 수 있다. 다만 함수 type을 사용하려면 기존처럼 함수를 만드는 게 아닌 다른 방식을 사용해야 한다.
// string 타입의 변수를 받아 number를 반환
type FuncType = (a: string) => number;
let func: FuncType = function (x) {
return parseInt(x);
};
method에 type alias 지정
object 자료형에는 함수 또한 넣어서 활용할 수 있다.
let info = {
name: 'kim',
plusOne(a) {
return a + 1;
},
changeName: () => {
console.log('Hi~');
}
}
info.plusOne(1);
info.changeName();
TS를 통한 HTmL 변경, 조작 시 주의사항
document.getElementById()를 TS에서는 어떻게 사용하나?
HTML 조작 시 narrwing 하는 방법
let title = document.querySelector("#title");
1. 변수 != null
if (title !== null) {
title.innerHTML = "반가워요";
}
2. instanceof 연산자 (가장 많이 사용)
if (title instanceof Element) {
title.innerHTML = "반가워요";
}
다만 Element 뿐만 아니라 이를 상속받은 다양한 ~~Element 객체들이 있어 이를 활용해야 한다.
예를 들어 <a> 태그의 href 속성의 경우 HTMLAnchorElement, <button> 태그의 경우 HTMLButtonElement 등을 사용해야 하는 식이다.
3. as 문법 사용하기 (사용 지양)
let title = document.querySelector("#title") as Element;
4. 오브젝트에 ? 붙이기
if (title?.innerHTML != undefined) {
title.innerHTML = "반가워요";
}
5. tsconfig에서 null check 끄기 (사용 지양)
(JS) Constructor, Class 알아보기
Constructor 문법은 많은 양의 비슷한 object를 찍어낼 때 사용한다.
function machine() {
this.name = 'kim'
this.age = 15
this.sayHi = function () {
console.log('안녕하세요! ' + this.name + '님!')
}
}
let s1 = new machine();
console.log(s1); // machine {name: 'kim', age: 15}
s1.sayHi() // 안녕하세요! kim님!
위의 machine 같은 object 생성기를 Constructor, 그리고 Constructor에서 생성된 Object를 Instance라고 부른다.
이때 새로 만들 object의 내부 값을 바꾸고 싶다면, function에 파라미터를 받아 그 값을 내부 값으로 설정하면 된다.
function machine(a, b) {
this.name = a
this.age = b
this.sayHi = function () {
console.log('안녕하세요! ' + this.name + '님!')
}
}
let s1 = new machine('jkh', 27);
console.log(s1); // machine {name: 'jkh', age: 27}
s1.sayHi() // 안녕하세요! jkh님!
(JS) Prototype 문법
Constructor에 몰래 생성된 공간을 prototype(원형, 유전자)이라고 한다. 여기에는 자식(Instance)에게 전해줄 정보들을 기록해 놓을 수 있다. 이 prototype의 내용은 자식 object에 복사되지는 않지만 가져다가 쓸 수 있다.
컨스트럭터명.prototype.키 = '값'
Object 안의 x 필드의 값 출력 시, (1) 해당 object 안에 x 필드가 있는지, (2) object의 부모 prototype에 x 필드가 있는지를 검사한다. 이는 부모가 없을 때까지 반복된다. 따라서 특정 object안에 어떤 필드가 존재하지 않더라도, 해당 object의 부모에게 그 필드가 존재한다면 이를 활용할 수 있다.
예를 들어 array 자료형 또한, 이 prototype을 활용해서 push, forEach, sort 등의 기본 함수들을 사용할 수 있다.
정리하자면, constructor에 넣은 변수, 함수는 자식 object(인스턴스)에게 그대로 복사된다. 반면 prototype에 넣은 변수, 함수는 자식 object에는 복사되지 않지만 자식 인스턴스가 이를 끌어다 쓸 수 있다.
Class 생성과 타입 지정
ES6 이상에서는 class를 지원하므로 이를 활용해 인스턴스를 생성할 수 있다.
클래스 안의 필드 값은 기존 JS의 constructor와 같은 역할을 한다.
class Person {
data = 123;
}
let p1 = new Person();
console.log(p1.data); // 123
그리고 constructor 함수를 통해 실제 인스턴스 각각이 가질 값을 부여할 수 있다. 이때 TS에서는 constructor에 쓸 필드명을 class 필드로 미리 선언해 놓고 자료형을 명시해야 한다.
class Person {
name: string;
constructor(a: string) {
this.name = a;
}
}
let p1 = new Person("john");
console.log(p1.name); // john
prototype 또한 class에서도 동일하게 사용할 수 있는데, class 안에 메서드를 넣으면 인스턴스에 해당 함수는 없지만, prototype으로써 해당 메서드를 끌어다가 쓸 수 있다.
class Person {
name: string;
constructor(a: string) {
this.name = a;
}
sayHi(a: string) {
console.log("Hi~" + a);
}
}
let p1 = new Person("john");
p1.sayHi("jkh"); // Hi~jkh
Interface
object 자료형의 타입 변수는 기존처럼 type 키워드로 만들 수도 있지만, interface라는 키워드로도 만들 수 있다.
// 방법 1: type 키워드
type Square = {color: string, width: number}
// 방법 2: interface 키워드
interface Square {
color: string;
width: number;
}
let sqr:Square = {color: 'red', width: 100}
interface를 사용할 경우, type과 다르게 extends를 통해 복사할 수 있다.
interface Student {
name: string;
}
interface Teacher extends Student {
age: number;
}
let student: Student = { name: "kim" };
let teacher: Teacher = { name: "kim", age: 20 };
물론 type 또한 & 기호를 통해 동일하게 복사가 가능하다. 때문에 기존 타입 복사 가능 여부는 결정적인 차이라고 할 수 없다. 결정적으로는, interface는 중복 선언이 가능하지만, type은 중복 선언이 불가능하다.
interface를 중복 선언할 경우, 두 interface 안의 속성들이 합쳐진다. (자동 extends) 이를 활용해서, 외부 라이브러리같은 경우 interface를 많이 사용하고, 이를 통해 추후 타입 추가를 용이하게 한다.
만약 interface 중복 선언 시 속성이 중복된다면, error로 잡아준다.
참고자료
코딩애플 온라인 강의 <빠르게 마스터하는 타입스크립트>
'크래프톤 정글 > Equipped in 정글(나만무)' 카테고리의 다른 글
[나만무] 250707 일지 (1) | 2025.07.07 |
---|---|
[나만무] 250704 일지 (2) | 2025.07.04 |
[나만무] Jungle-Board 댓글 기능 및 검색, 페이지네이션 구현 및 배포하기 (2) | 2025.06.20 |
[나만무] Jungle-Board 로그인/회원가입 기능 구현 및 게시판 권한 반영하기 (0) | 2025.06.18 |
[나만무] 06.17 TIL - Next.js 학습 by 코딩애플 (0) | 2025.06.17 |