TypeScript는 JavaScript에 “정적 타입”이 추가된 언어이다. 따라서 TypeScript는 JavaScript와 타입 두 가지를 모두 포함하고 있는 큰 집합, 즉 JS의 상위 집합(Superset)이라고 할 수 있다.
string , number , boolean , object , Array , tuple , any , null , undefined// 기본 형태
const fruits: string[] = [`apple` , `banana` , `orange`];
// 구 버전
const fruits:Array<string> = ['apple', 'banana', 'orange'];
const arr:[string, number] = ['apple', 10];
⇒ tuple 타입은 타입 오퍼레이터( | , & ) 를 활용하면 사실상 많이 쓰이지 않는다.
⇒ 하지만, 특정 순서와 길이를 갖춘 배열을 정의할 때는 여전히 유용하다 !
let tuple: [string, number]; // 첫 번째 요소는 string, 두 번째 요소는 number
tuple = ["hello", 42]; // ✅ 가능
tuple = [42, "hello"]; // ❌ 오류! 순서와 타입이 맞지 않음
const arr:any = 10;
any 타입은 타입스크립트에서 유연성과 편리함을 제공하기 위해 존재하지만, 사실 타입 스크립트의 주요 장점인 타입 안정성과는 모순되는 면이 있다.
하지만 개발자는 그러한 면이 필요할 때가 있다. 특히나 any 는 타입스크립트의 타입 시스템을 우회할 수 있게 해줘서 타입을 강제하지 않고 유연하게 작업을 할 수 있게 해준다.
예를 들어, 개발 중에 유연한 코드를 작성해야 할 때 사용될 수 있다.
any 를 사용하면 타입 체크를 하지 않고도 코드가 동작할 수 있다!any 를 사용하여 빠르게 프로토타입을 작성할 수 있다!하지만!, 나중에 코드 유지보수가 어려워질 수 있고, 여러 곳에서 any 를 사용하면, 버그를 찾기 어려운 코드가 될 수 있다는 점을 명심하며, any 사용은 지양하는 것이 맞다!
⇒ 대신 unknown 를 사용할 수 있다. unknown 은 타입 체크를 거친 후에만 다른 타입으로 변환할 수 있어서, any 보다는 더 안전하게 사용할 수 있다.
let value: unknown = "hello";
value = 42; // ✅ 가능
// value를 다른 타입으로 변환하려면 타입 체크가 필요
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ 가능
}
console.log() → 출력이 되는거지 반환하는 것은 아니다.throw new Error(error) → 예외를 던지는 경우 (정상 종료❌)함수 타입 지정은 매개변수와 반환값에 타입을 지정해주면 된다.
function add(a: number, b: number): number {
return a + b;
}
함수 표현식은 타입을 2가지로 지정할 수 있다.
- 변수 자체에 함수 타입을 지정 ( 함수 타입 선언 )
const subtract: (a: number, b: number) => number = function (a, b) {
return a - b;
};
장점 : 변수 타입과 함수 정의를 분리함으로, 타입만 따로 관리할 때 유용하며, 타입 오류를 조기에 발견할 수 있다.
단점 : 가독성이 떨어질 수 있다. TS가 타입 추론을 하지 않는다.
const subtract = function (a: number, b: number): number {
return a - b;
};
장점 : 가독성이 높으며, TS의 타입 추론을 최대한 활용할 수 있다.
단점 : 함수 전체의 타입을 한 번에 확인하기 어렵다. 복잡한 함수의 경우 코드가 길어질 수 있다.
복잡한 함수 타입을 정의해야 하는 경우 → 함수 타입 선언
type MathOperation = (a: number, b: number) => number;
const multiply: MathOperation = (a, b) => a * b;
단순한 함수나 타입을 따로 관리할 필요가 없는 경우 → 개별 타입 지정 ( 일반적인 상황 👍)
const subtract = (a: number, b: number): number => a - b;
함수 표현식과 동일한 방식으로 타입을 지정
const multiply: (a: number, b: number) => number = (a, b) => a * b;
const multiply = (a: number, b: number): number => {
return a * b;
};
⇒ 콜백 함수에서 명시적 타입 선언이 더 안전하고 재사용성이 좋다!
// 타입을 별도로 선언하지 않은 콜백
const numbers = [1, 2, 3];
numbers.map(num => num * 2); // 타입 추론이 자동으로 작동
// 함수 타입을 명시적으로 선언한 콜백
const double: (num: number) => number = num => num * 2;
numbers.map(double);
옵셔널 파라미터 (
?) : 타입으로 지정한 매개변수를 생략할 수 있다.
| )let value: string | number; // string 또는 number일 수 있음
value = "hello"; // OK
value = 42; // OK
value = true; // 오류: 'boolean'은 'string | number'에 할당할 수 없음& )type Person = { name: string };
type Employee = { employeeId: number };
let worker: Person & Employee = {
name: "John",
employeeId: 1234
}; // OK타입 가드란?
: 코드에서 유니언 타입을 구체적인 타입으로 좁히는 방법이다. 이를 통해 타입스크립트가 특정 조건을 만족할 때 해당 타입에 맞는 속성이나 메서드를 안전하게 사용할 수 있게 해준다.
typeof를 이용한 타입 가드function printLength(value: string | number) {
if (typeof value === "string") {
// value는 이제 string 타입으로 좁혀짐
console.log(value.length);
} else {
// value는 number 타입으로 좁혀짐
console.log(value.toFixed(2));
}
}
printLength("Hello"); // 출력: 5
printLength(123.456); // 출력: 123.46
instanceof를 이용한 타입 가드class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function speak(animal: Dog | Cat) {
if (animal instanceof Dog) {
// animal은 Dog 타입으로 좁혀짐
animal.bark();
} else {
// animal은 Cat 타입으로 좁혀짐
animal.meow();
}
}
const dog = new Dog();
const cat = new Cat();
speak(dog); // 출력: Woof!
speak(cat); // 출력: Meow!
🚨
함수 오버로드를 사용한다면, 유니언 타입을 처리할 때 타입 가드가 필요 없다.
하나의 함수가 여러 개의 호출 시그니처(매개변수와 반환값 타입)을 가질 수 있게 하는 기능
⇒ 이를 통해 함수가 입력에 따라 다른 동작을 수행하도록 정의할 수 있다.
함수 오버로드는 2가지 부분으로 구성됩니다.
- 오버로드 시그니처 : 함수의 다양한 사용 방법을 정의 ( 정의만, 구현❌)
- 구현부 : 오버로드된 모든 시그니처를 처리하는 실제 함수
// 오버로드 시그니처 정의
function processInput(input: string): string; // 문자열을 입력받을 경우
function processInput(input: number): number; // 숫자를 입력받을 경우
// 구현부 정의
function processInput(input: string | number): string | number {
if (typeof input === "string") {
return input.toUpperCase(); // 문자열이면 대문자로 변환
} else {
return input * 2; // 숫자면 두 배로 반환
}
}
// 사용
const result1 = processInput("hello"); // 'HELLO'
const result2 = processInput(10); // 20
function processInput(input: string | number): string | number {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input * 2;
}
}
const result1 = processInput("hello"); // 타입 추론: string | number
const result2 = processInput(10); // 타입 추론: string | number
⇒ 그 이유 중에 하나로 호출한 결과의 타입이 항상 string | number 로 나오는 것에 있다.
const result = processInput("hello");
// 타입: string | number
// result가 string인지 number인지 타입스크립트가 확신하지 못함 ⇒ 이로 인해 결과를 사용할 때, 추가적인 타입 검사를 해야한다.if (typeof result === "string") {
console.log(result.toUpperCase());
} else {
console.log(result.toFixed(2));
} ⇒ 위의 코드가 동작은 하지만 ,TS가 함수의 입력과 반환값의 관계를 자동으로 보장하지 않기 때문에 귀찮고 실수할 가능성이 커진다. 반면, 오버로드를 사용한다면// 오버로드 시그니처
function processInput(input: string): string;
function processInput(input: number): number;
// 구현부
function processInput(input: string | number): string | number {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input * 2;
}
}
const result1 = processInput("hello"); // 타입: string
console.log(result1.toUpperCase()); // 안전하게 사용 가능
const result2 = processInput(10); // 타입: number
console.log(result2.toFixed(2)); // 안전하게 사용 가능// 타입 선언이 과도한 경우
const multiply: (x: number, y: number) => number = (x, y) => x * y;
// 타입 추론 활용
const multiply = (x: number, y: number) => x * y;
| 구분 | 타입 추론 | 타입 명시 |
|---|---|---|
| 정의 | 타입스크립트가 자동으로 타입을 추론 | 개발자가 명시적으로 타입을 지정 |
| 주요 특징 | - 값이나 표현식을 기반으로 타입을 추론 - 타입을 자동으로 결정 | - 타입을 명확하게 선언 - 추론보다 우선 적용 |
| 장점 | - 코드가 간결해짐 - 타입을 자동으로 설정해 줌 | - 명확한 타입 설정 - 실수 방지 |
| 단점 | - 복잡한 타입의 경우 추론이 어려울 수 있음 | - 코드가 길어질 수 있음 - 명시해야 하는 경우가 많음 |
: 특정 값에만 고정된 타입으로, 타입스크립트에서 값 자체가 타입이 되는 것을 의미한다.
let greeting: "hello"; // "hello"라는 값만 가질 수 있음
greeting = "hello"; // ✅ 가능
greeting = "world"; // ❌ 오류! ("hello"만 가능)
const 선언 과 let 선언const : 값이 변경되지 않기 때문에, 타입스크립트는 이를 리터럴 타입으로 추론한다.let : 값이 바뀔 가능성이 있기 때문에, 더 넓은 범주의 타입 (ex - string , number )으로 추론한다.const fixedValue = "hello"; // 타입: "hello" (리터럴 타입)
let flexibleValue = "hello"; // 타입: string (일반 타입)
flexibleValue = "world"; // ✅ 가능 (string이기 때문)
⇒ But, let 도 타입을 명시하면, 리터럴 타입으로 사용할 수 있다.
let restrictedValue: "hello";
restrictedValue = "hello"; // ✅ 가능
restrictedValue = "world"; // ❌ 오류!
: 코드에서 값의 범위를 제한하고 실수를 방지하며 안정성을 높이는 데 유용하다.
상태 관리
type ButtonState = "enabled" | "disabled";
let button: ButtonState = "enabled"; // "enabled" 또는 "disabled"만 가능
button = "disabled"; // ✅ 가능
button = "loading"; // ❌ 오류! (정의되지 않은 값)
HTTP 메서드 제한
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
let method: HttpMethod = "GET"; // 허용된 값만 가능
method = "PATCH"; // ❌ 오류! ("GET", "POST", "PUT", "DELETE"만 가능)
테마 설정
type Theme = "light" | "dark";
let currentTheme: Theme = "light"; // "light" 또는 "dark"만 가능
currentTheme = "dark"; // ✅ 가능
currentTheme = "blue"; // ❌ 오류!
const 로 선언하면 자동으로 리터럴 타입이 되지만, let 은 기본적으로 일반 타입이 된다.let 은 타입 명시를 통해 리터럴 타입으로 사용할 수 있다.| 특징 | 옵셔널 메서드 | 옵셔널 속성 |
|---|---|---|
| 호출/접근 방식 | 호출 전에 반드시 존재 여부 확인 필요 | 읽기만 하는 경우 타입 가드 불필요 |
| 이유 | 호출 시 undefined 호출로 에러 발생 가능 | 읽기 시 단순히 undefined 반환 |
| 타입 가드가 필요한 경우 | 메서드를 호출하려 할 때 | 값을 사용하려 할 때 (toUpperCase 등) |
undefined 가능성을 고려해야 합니다.따라서 타입 가드의 필요 여부는 속성/메서드의 사용 맥락과 호출 방식의 안전성에 따라 결정됩니다.