1. 리터럴 타입
1.1. 리터럴 타입
리터럴 타입은 타입스크립트에서 고정된 특정 값 자체를 타입으로 사용하는 것을 의미한다. 일반적인 타입인 string, number, boolean 등은 여러 값의 집합을 나타내지만, 리터럴 타입은 특정 값을 지정해 해당 값만 가질 수 있도록 제한한다. 리터럴 타입은 특정 값만을 허용하기 때문에, 코드에서 고정된 값의 사용을 강제하거나 제한된 옵션을 줄 때 유용하게 쓸 수 있다.
예를 들어 const name = "Alice"; 라고 값을 지정하면 "Alice" 는 리터럴 값에 해당된다. 이때 "Alice" 를 타입으로 사용할 수도 있으며, 이를 리터럴 타입이라고 한다.
- 문자열 리터럴 타입: 특정 문자열 값으로 제한되는 타입
let direction: "left" | "right" | "up" | "down";
direction = "left"; // 가능
direction = "forward"; // 오류: "forward"는 지정된 리터럴 타입에 포함되지 않음
- 숫자 리터럴 타입: 특정 숫자 값으로 제한되는 타입
let step: 1 | 2 | 3;
step = 2; // 가능
step = 4; // 오류: 4는 지정된 리터럴 타입에 포함되지 않음
- 불리언 리터럴 타입: true / false 중 하나의 고정된 값을 가지도록 설정할 수 있음
let isEnabled: true;
isEnabled = true; // 가능
isEnabled = false; // 오류: false는 허용되지 않음
- 객체 리터럴 타입: 객체 형태를 정의할 때 사용하는 타입으로, 특정 구조와 속성을 갖는 객체를 선언하는 방식
const person = {
name: "Alice",
age: 25,
greet: function() {
console.log("Hello, " + this.name);
}
};
리터럴 타입의 특징을 정리해보면 아래와 같다.
1. 리터럴 타입은 고정된 값 집합만을 가질수 있도록 제한하여, 예상치 못한 값을 방지하고 코드의 안정성을 높임 2. 리터럴 타입은 타입 좁히기와 함께 사용하면 코드의 가독성과 타입 안정성이 향상됨 3. 자주 사용하는 고정된 값을 관리하기 쉬워지며, 잘못된 값을 미리 방지할 수 있음 |
1.2. 인터페이스를 구현한 클래스에서의 리터럴 타입
Student 인터페이스를 구현한 MyStudent 클래스에서 gender 타입을 그냥 'male' 로 지정했더니 에러가 발생했다.
Property 'gender' in type 'MyStudent' is not assignable to the same property in base type 'Student'.
Type 'string' is not assignable to type '"male" | "female"'.
에러 메세지에서는 Type 'string' is not assignable to type '"male" | "female"' 라고 말하고 있는데 해석해보면 문자열 타입이 유니온 타입에 맞지 않는다고 말하고 있다 -> gender 가 문자열 타입으로 해석되었기 때문
interface Student {
stdId: number;
stuName?: string;
age?: number;
gender?: 'male' | 'female';
course?: string;
completed?: boolean;
setName?: (name: string) => void,
getName?: () => string
}
class MyStudent implements Student {
stdId = 2;
stuName = 'lee';
age = 30;
gender = 'male'; // 이 부분
course = 'javascript';
completed = true;
setName(name: string): void {
this.stuName = name;
console.log('이름 설정: ' + this.stuName);
}
}
에러를 해결은 간단하다 gender 의 타입이 정확하게 "male" | "female" 유니온 타입으로 지정해주면 된다.
gender: 'male' | 'female' = 'male'; // 타입을 명확하게 지정해준 뒤 값을 할당
2. Union / any 타입
2.1. any 타입
any 타입은 아무 타입이나 들어올 수 있다. 하지만, any 타입이 불분명해서 컴파일 과정에서 타입 에러를 잡지 못할 수 있기 때문에 최대한 사용하지 않는 것이 좋다.
그렇다면 any 타입은 왜 존재하는 것일까?
-> 작업 중인 코드의 타입 명시가 어려운 경우 사용하기 위해 (동적 데이터의 타입을 알 수 없을 때)
let anyVal: any = 100; // 숫자
anyVal = '문자' // 문자
2.2. Union 타입
제한된 타입을 동시에 지정하고 싶을 때 사용한다. | 기호를 사이에 두고 동시에 타입을 지정할 수 있다.
let unionVal: number | string;
유니온 타입을 사용할 때는 typeof 연산자를 이용해서 타입 검증을 수행하는 타입 가드를 사용해주는 것이 좋다.
function printValue(value: string | number) {
if (typeof value === "string") { // string 타입으로 좁혀짐
console.log("문자열 값:", value.toUpperCase());
} else { // number 타입으로 좁혀짐
console.log("숫자 값:", value.toFixed(2));
}
}
printValue("hello"); // 출력: 문자열 값: HELLO
printValue(123.456); // 출력: 숫자 값: 123.46
3. 타입 별칭 (Type Alias)
타입 별칭은 타입스크립트에서 type 키워드를 사용해 타입에 새로운 이름을 붙여주는 기능이다. 이를 통해 복잡한 타입을 간단하게 표현하고 재사용할 수 있다.
type strOrNum = number | string; // 재사용할 타입 정의
let numStr: strOrNum = '100';
function convertToString(val: strOrNum): string {
return String(val);
}
function convertToNumber(val: strOrNum): number {
return Number(val);
}
console.log(converToString(numStr));
유니온 타입을 사용하면 변수나 매개변수가 여러 타입 중 하나를 가질 수 있게 정의할 수 있다.
type Id = string | number;
const userId: Id = "user123"; // 또는 userId: Id = 123;
교차 타입을 사용하면 여러 객체 타입을 결합하여 새로운 타입을 만들 수 있다.
type Person = { name: string };
type Employee = { employeeId: number };
type EmployeeInfo = Person & Employee;
const emp: EmployeeInfo = {
name: "John",
employeeId: 101,
};
타입 별칭을 사용해 함수의 형태도 정의할 수 있다.
type Greet = (name: string) => string;
const greetUser: Greet = (name) => `Hello, ${name}!`;
3.1. 인터페이스 vs 타입 별칭
특징 | 타입 별칭 (Type Alias) | 인터페이스 (Interface) |
확장 | & 연산자 사용 | extends 키워드 사용 |
객체 외 타입 정의 | 가능 | 불가능 (객체만) |
유니온/교차 타입 지원 | 가능 | 불가능 |
주요 사용 상황 | 복잡한 타입 조합, 객체 외 타입 정의 시 | 객체 구조 정의, 확장성 필요 시 |
- 확장 방식의 차이
- 인터페이스의 확장 -> extends 키워드 사용
- 리터럴 타입 확장 -> & 연산자 사용
// 인터페이스의 확장
interface Person {
name: string;
}
interface Employee extends Person {
employeeId: number;
}
// 리터럴 타입 확장
type PersonType = { name: string };
type EmployeeType = PersonType & { employeeId: number };
- 선언 병합
- 인터페이스는 같은 이름으로 여러 번 선언 가능 + 선언들이 자동으로 병합됨
- 객체 리터럴 타입은 선언 병합을 지원하지 않으며, 동일한 이름으로 두 번 선언하면 오류 발생
// 인터페이스 병합 예시
interface User {
name: string;
}
interface User {
age: number;
}
// 결과적으로 User는 { name: string; age: number; }
type UserType = { name: string };
type UserType = { age: number }; // 오류 발생 (타입 별칭은 병합되지 않음)
- 타입의 유연성
- 객체 리터럴 타입은 객체뿐 아니라 유니온 타입, 교차 타입, 원시 타입 등 다양한 형태를 정의할 수 있음
- 인터페이스는 객체 형태만을 정의할 수 있음 (유니온, 기본 타입 등은 X)
// 객체 리터럴 타입의 유니온 예시
type Shape = Circle | Square; // 가능
// 인터페이스로는 불가
interface Shape = Circle | Square; // 오류
4. Array, Tuple
4.1. Array
// 타입스크립트에서 배열 선언
let numbers: number[] = [1,2,3,4,5];
let fruits: string[] = ['apple', 'banana', 'orange'];
let mixedArray: (number | string)[] = [1, 'two', 3, 'four'];
// 읽기 전용 배열 -> 수정 불가
let readOnlyArray: ReadonlyArray<number> = [1, 2, 3];
4.2. Tuple
튜플은 자바스크립트에는 없는 데이터 타입으로 배열의 형식을 가지고 있지만, 일반 배열과는 다르게 타입의 순서가 정해져있다.
let greeting: [number, string, boolean] = [1, 'hello', true];
※ 추가 - Array 와 Tuple 의 차이점
- Array 는 길이가 가변적이며 동일한 타입의 요소로 구성
- Tuple 은 길이가 고정적이며 각 요소의 타입이 정해져 있다.
5. Class
class Employee {
empName: string;
age: number;
empJob: string;
printEmp = () => {
console.log(`${this.empName}의 나이는 ${this.age}이고, 직업은 ${this.empJob}입니다.`);
}
}
let employee1 = new Employee();
employee1.empName = 'kim';
employee1.age = 20;
employee1.empJob = '개발자';
employee1.printEmp();
5.1. 생성자
constructor 는 타입스크립트에서 클래스의 인스턴스를 초기화하는 특별한 메서드이다. 객체가 생성될 때 자동으로 호출되며, 주로 객체의 초기 속성 값을 설정하는 역할을 한다.
- consructor 키워드 사용
- new 키워드를 사용해 클래스의 인스턴스를 생성할 때 호출됨
- 필요에 따라 매개변수를 받아 객체 속성을 동적으로 초기화할 수 있음
- this 키워드를 사용해 클래스의 속성에 접근하고 초기값을 설정
class Employee {
empName: string;
age: number;
empJob: string;
// 생성자 메서드
constructor(empName: string, age: number, empJob: string) {
this.empName = empName;
this.age = age;
this.empJob = empJob;
}
printEmp = () => {
console.log(`${this.empName}의 나이는 ${this.age}이고, 직업은 ${this.empJob}입니다.`);
}
}
let employee1 = new Employee('kim', 20, '개발자');
5.2. 접근 지정자
객체 지향에서는 캡슐화 철학을 지키기 위해 접근 지정자를 제공한다.
- public -> 어디서나 접근 가능, 클래스 외부에서도 자유롭게 사용 가능 / 접근 지정자를 명시하지 않으면 public
- private -> 클래스 내부에서만 접근 가능하며, 외부에서는 접근할 수 없음 (상속 클래스에서도 불가능)
- protected -> 해당 클래스와 상속 클래스에서만 접근 가능, 외부에서는 접근 불가지만 상속 받은 클래스에서는 사용할 수 있음
- public
class Example {
public name: string;
constructor(name: string) {
this.name = name;
}
}
const example = new Example("Alice");
console.log(example.name); // 정상 출력
- private
class Example {
private secret: string;
constructor(secret: string) {
this.secret = secret;
}
}
const example = new Example("mySecret");
// console.log(example.secret); // 오류 발생 (private이므로 접근 불가)
- protected
class Parent {
protected value: number;
constructor(value: number) {
this.value = value;
}
}
class Child extends Parent {
printValue() {
console.log(this.value); // 정상 접근
}
}
const child = new Child(10);
// console.log(child.value); // 오류 발생 (protected이므로 외부 접근 불가)
5.3. getter & setter
private 으로 설정된 멤버변수에 접근하기 위해서는 타입스크립트에서는 getter / setter 를 제공하고 있다.
getter
- 속성의 값을 가져오는 역할
- get 키워드를 사용하여 정의
setter
- 속성 값을 설정하는 역할
- set 키워드를 사용하여 정의
(_ 언더바는 클래스 내부에서만 사용하는 private 속성/메서드를 나타내기 위해 붙이는 것)
class Person {
private _age: number;
constructor(age: number) {
this._age = age;
}
// getter
get age(): number {
return this._age;
}
// setter
set age(value: number) {
if (value < 0) {
console.log("Age cannot be negative.");
return;
}
this._age = value;
}
}
const person = new Person(25);
console.log(person.age); // getter 호출 -> 25
person.age = 30; // setter 호출 -> _age가 30으로 변경
person.age = -5; // setter 호출 -> 오류 메시지 출력
'TIL with Programmers' 카테고리의 다른 글
[TIL] 10/31 클래스/함수형 컴포넌트, useState (0) | 2024.10.31 |
---|---|
[TIL] 10/30 리액트 기초-1 (0) | 2024.10.30 |
[TIL] 10/28 타입스크립트 기초-1 (0) | 2024.10.28 |
[TIL] 10/22 JS 기초2 (2) | 2024.10.22 |
[TIL] 10/21 JS 기초 (2) | 2024.10.22 |