
TypeScript는 단순히 타입을 붙이는 것을 넘어, 코드의 안정성을 높이고 개발자의 실수를 미연에 방지하는 강력한 도구입니다. 기본적인 선언 방법부터 실무 활용 팁까지 정리해 보겠습니다.
가장 많이 사용되는 방식입니다. 복잡한 객체 구조에 이름을 붙여 재사용할 수 있게 해줍니다.
type User = {
id: number;
name: string;
nickname: string;
birth: string;
};
const user: User = {
id: 1,
name: "홍길동",
nickname: "dev_osoli",
birth: "1998.11.12",
};
객체의 속성 이름은 동적이지만, 키와 값의 타입 규칙이 일정한 경우에 유용합니다.
type CountryCodes = {
[key: string]: string;
};
let countryCodes: CountryCodes = {
Korea: 'ko',
UnitedState: 'us',
};
💡 활용 팁: 수많은 국가 코드나 설정값처럼 키값이 계속 늘어날 수 있는 구조에서 코드의 중복을 획기적으로 줄여줍니다.
?와 !TypeScript에서 이 두 기호는 데이터의 존재 여부를 다룰 때 핵심적인 역할을 합니다.
| 기호 | 이름 | 의미 |
|---|---|---|
? | 옵셔널 (Optional) | "이 값은 있을 수도 있고 없을 수도 있음" (선택) |
! | 단언 (Assertion) | "이 값은 절대 null/undefined가 아님을 내가 보장함" (확신) |
interface Person {
name: string;
tall?: number;
}
function introduce(person: Person) {
// 1. 단언 (!) 사용 시
// 개발자가 "난 이 함수 호출할 때 무조건 tall을 넣을 거야"라고 확신할 때
console.log(person.tall!.toFixed());
// 2. 옵셔널 체이닝 (?.) 사용 시
// 값이 없으면 에러 대신 undefined를 반환 (toFixed는 호출되지 않음)
console.log(person.tall?.toFixed());
// 3. 조건문 (Safe Guard) 사용 시 - 가장 권장되는 방법
// 타입 가드를 통해 블록 내부에서 tall을 'number'로 확정시킴
if (person.tall) {
console.log(person.tall.toFixed()); // 여기선 !나 ?가 없어도 안전함
}
}
여러 타입을 합성하여 새로운 타입을 만드는 방법입니다.
|): 여러 타입 중 하나일 수 있음을 의미합니다.&): 모든 타입의 속성을 전부 가져야 함을 의미합니다.type Dog = { name: string; color: string; };
type Person = { name: string; language: string; };
type Union = Dog | Person; // Dog이거나 Person이거나
type Intersection = Dog & Person; // Dog이면서 동시에 Person (name, color, language 모두 필요)
객체 타입들에 공통된 리터럴 속성(tag, state 등)을 부여하여 타입을 확실하게 구분하는 기법입니다.
비동기 작업의 상태를 관리할 때, ?를 남발하면 if문 안에서 타입 추론이 제대로 되지 않아 에러가 발생할 수 있습니다. 하지만 서로소 유니온을 사용하면 안전하게 좁히기(Narrowing)가 가능합니다.
type LoadingTask = { state: 'LOADING' };
type FailedTask = { state: 'FAILED'; error: { message: string } };
type SuccessTask = { state: 'SUCCESS'; response: { data: string } };
type AsyncTask = LoadingTask | FailedTask | SuccessTask;
function processResult(task: AsyncTask) {
switch (task.state) {
case 'LOADING':
console.log('로딩 중');
break;
case 'FAILED':
// task가 FailedTask임이 확실해져서 error 속성에 안전하게 접근 가능
console.log(`에러 : ${task.error.message}`);
break;
case 'SUCCESS':
console.log(`성공 : ${task.response.data}`);
break;
}
}
객체의 구조를 정의하는 '약속'입니다. 메서드 오버로딩이나 선언 합치기가 가능합니다.
interface Person {
readonly name: string;
age?: number;
sayHi(): void;
}
객체를 생성하는 틀이며, extends를 통해 기능을 확장할 수 있습니다.
class Student {
constructor(public name: string, public grade: string) {}
}
class StudentDeveloper extends Student {
constructor(name: string, grade: string, public skill: string) {
super(name, grade); // 부모 생성자 호출
}
}
typeof: 실제 값에서 타입을 추출합니다.keyof: 객체 타입의 모든 키(key)들을 문자열 유니온 타입으로 가져옵니다.const person = { name: "홍길동", age: 27 };
// person 객체의 타입을 가져와서 그 키값들만 유니온으로 추출
type PersonKey = keyof typeof person; // "name" | "age"
TypeScript의 선언 방법은 다양하지만, 핵심은 "어떻게 하면 타입 에러를 줄이고 의도를 명확히 전달할 것인가"에 있습니다. 특히 서로소 유니온은 복잡한 상태 관리에서 빛을 발하는 기법이니 꼭 익혀두시길 권장합니다!