API 엔드포인트: https://api.example.com/users
응답 예시:
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": "12345"
},
"phone": "123-456-7890",
"website": "https://example.com"
}
// Address 타입 정의
interface Address {
street: string;
city: string;
zipcode: string;
}
// User 타입 정의
interface User {
id: number;
name: string;
email: string;
address: Address;
phone: string;
website: string;
}
API 호출 함수
fetch
를 사용하여 데이터를 호출하고 타입을 적용합니다.
async function fetchUsers(): Promise<User[]> {
const response = await fetch("https://api.example.com/users");
if (!response.ok) {
throw new Error("Failed to fetch users");
}
return await response.json();
}
: 이메일과 웹사이트URL 전화번호 타입 구체화 하기
// 이메일 타입 정의
type EmailAddress = `${string}@${string}.${string}`;
// 웹사이트 URL 타입 정의
type WebsiteURL = `http${"s" | ""}://${string}.${string}`;
// 전화번호 타입 정의
type PhoneNumber = `${number}-${number}-${number}`;
// Address 타입 정의
interface Address {
street: string;
city: string;
zipcode: `${number}`; // 우편번호를 숫자로 제한
}
// User 타입 정의
interface User {
id: number;
name: string;
email: EmailAddress; // 엄격한 이메일 형식
address: Address;
phone: PhoneNumber; // 간단한 전화번호 형식
website: WebsiteURL; // 웹사이트 URL 형식
}
올바른 값
const user: User = {
id: 1,
name: "John Doe",
email: "john.doe@example.com",
address: {
street: "123 Main St",
city: "Anytown",
zipcode: "12345"
},
phone: "123-456-7890",
website: "https://example.com"
};
잘못된 값 (컴파일 오류 발생)
const invalidUser: User = {
id: 2,
name: "Jane Doe",
email: "jane.doe[at]example.com", // 오류: 이메일 형식이 아님
address: {
street: "456 Elm St",
city: "Othertown",
zipcode: "abcde" // 오류: 숫자 형식이 아님
},
phone: "1234567890", // 오류: 전화번호 형식이 아님
website: "example.com" // 오류: URL 형식이 아님
};
: 있으면 하자 !
any, unknown 특징
특징 | unknown | any |
---|---|---|
안전성 | 타입을 사용하기 전에 검사 필수 | 모든 작업 허용 (타입 검사 없음) |
타입 검사 | 엄격 (타입을 좁혀야만 사용 가능) | 느슨함 (타입 체크가 무시됨) |
IntelliSense | 타입 좁히기 후 사용 가능 | IntelliSense 거의 없음 |
사용 시기 | 외부 데이터, 알 수 없는 타입 | 빠른 프로토타이핑 또는 유연성이 필요할 때 |
unknown
을 사용할까?: unknown
은 타입 안전성을 유지해야 하지만 데이터의 구체적인 타입을 사전에 알 수 없는 경우에 적합합니다.
async function fetchData(url: string): Promise<unknown> {
const response = await fetch(url);
return response.json();
}
async function processData() {
const data: **unknown**= await fetchData("https://api.example.com/data");
// 타입 좁히기 필요
if (Array.isArray(data)) {
console.log(`Array of length: ${data.length}`);
} else if (typeof data === "object" && data !== null) {
console.log(`Object with keys: ${Object.keys(data)}`);
} else {
console.log("Unknown data type");
}
}
unknown
은 강제로 타입을 좁혀야 하기 때문에 더 안전하게 데이터를 사용할 수 있습니다.
function processValue(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // 안전
} else if (typeof value === "number") {
console.log(value.toFixed(2)); // 안전
} else {
console.log("Unsupported type");
}
}
any
를 사용할까?: any
는 타입 안전성을 무시하고 빠르게 코드를 작성해야 할 때 사용합니다. 하지만 남용을 피해야 하며, 가능한 빨리 정확한 타입으로 대체하는 것이 좋습니다.
개발 초기 단계에서 타입 정의가 아직 명확하지 않거나, 많은 데이터를 테스트해야 할 때 유용합니다.
function logData(data: any) {
console.log(data);
}
logData({ id: 1, name: "John Doe" });
logData("This is a string");
logData(42);
특정 로직이 타입에 구애받지 않고 모든 데이터 타입을 다뤄야 할 때 사용합니다.
function mergeObjects(obj1: any, obj2: any): any {
return { ...obj1, ...obj2 };
}
JSON.parse()
, 로깅.unknown
vs any
사용 사례 비교const jsonData: **any** = JSON.parse('{"id": 1, "name": "John"}');
console.log(jsonData.toUpperCase()); // 런타임 오류 발생 가능 (TypeScript가 경고하지 않음)
const unknownData: **unknown** = JSON.parse('{"id": 1, "name": "John"}');
if (typeof unknownData === "string") {
console.log(unknownData.toUpperCase()); // 비교적 안전
} else {
console.log("Data is not a string");
}
any
: 런타임 오류 가능성이 있음.unknown
: 타입 검사 후 사용 가능, 타입 안전성 증가.{
"id": "12345",
"name": "Apple MacBook Pro",
"category": "laptop",
"price": 2499.99,
"currency": "USD",
"availability": {
"inStock": true,
"estimatedDeliveryDays": 5
},
"rating": {
"average": 4.8,
"reviews": 1245
}
}
우선, API 데이터 구조를 단순히 string
및 기타 기본 타입으로 표현합니다.
interface Product {
id: string; // 제품 ID
name: string; // 제품 이름
category: string; // 제품 카테고리
price: number; // 제품 가격
currency: string; // 통화 단위
availability: {
inStock: boolean; // 재고 여부
estimatedDeliveryDays: number; // 예상 배송일
};
rating: {
average: number; // 평균 평점
reviews: number; // 리뷰 수
};
}
string
으로 표현된 타입 이름을 해당 분야로 지정타입 이름을 구체화하여 해당 데이터와 연관된 도메인 용어를 반영합니다.
type ProductID = string;
type ProductName = string;
type ProductCategory = string;
type Currency = string;
interface Product {
id: ProductID;
name: ProductName;
category: ProductCategory;
price: number;
currency: Currency;
availability: {
inStock: boolean;
estimatedDeliveryDays: number;
};
rating: {
average: number;
reviews: number;
};
}
이 단계에서는 단순히 의미를 부여한 별칭을 도입했습니다. 타입 자체는 여전히 string
기반입니다.
string
지양하고 구체적인 타입으로 좁히기이제 더 구체적인 타입으로 좁힙니다. 예를 들어:
Currency
는 ISO 4217 표준 코드로 한정합니다.ProductCategory
는 정해진 카테고리 값으로 제한합니다.type ProductID = `${number}`; // ID는 숫자 기반 문자열
type ProductName = string;
type ProductCategory = "laptop" | "smartphone" | "tablet" | "accessory"; // 카테고리 제한
type Currency = "USD" | "EUR" | "JPY"; // ISO 4217 통화 코드
interface Product {
id: ProductID;
name: ProductName;
category: ProductCategory;
price: number;
currency: Currency;
availability: {
inStock: boolean;
estimatedDeliveryDays: number;
};
rating: {
average: number;
reviews: number;
};
}
이 단계에서는 string
대신 구체적인 값으로 제한하여 데이터 구조의 타입 안전성을 향상시켰습니다.
마지막으로, 상표
를 추가하여 타입의 고유성을 강화합니다. 상표를 추가하면 다른 동일 구조의 타입과 구별할 수 있습니다.
type ProductID = `${number}` & { _brand: "ProductID" };
type ProductName = string & { _brand: "ProductName" };
type ProductCategory = "laptop" | "smartphone" | "tablet" | "accessory" & { _brand: "ProductCategory" };
type Currency = "USD" | "EUR" | "JPY" & { _brand: "Currency" };
interface Product {
id: ProductID;
name: ProductName;
category: ProductCategory;
price: number;
currency: Currency;
availability: {
inStock: boolean;
estimatedDeliveryDays: number;
};
rating: {
average: number;
reviews: number;
};
}
ProductID
나 Currency
는 동일한 구조의 다른 string
과 구별됩니다.const macbook: Product = {
id: "12345" as ProductID,
name: "Apple MacBook Pro" as ProductName,
category: "laptop" as ProductCategory,
price: 2499.99,
currency: "USD" as Currency,
availability: {
inStock: true,
estimatedDeliveryDays: 5,
},
rating: {
average: 4.8,
reviews: 1245,
},
};
아래의 Product
타입은 특정 API 명세를 기반으로 설계되었습니다. 다음 중 올바르지 않은 부분은 무엇일까요
type ProductID = `${number}` & { _brand: "ProductID" };
type ProductCategory = "laptop" | "smartphone" | "tablet" | "accessory" & { _brand: "ProductCategory" };
type Currency = "USD" | "EUR" | "JPY" & { _brand: "Currency" };
interface Product {
id: ProductID;
name: string;
category: ProductCategory;
price: number;
currency: Currency;
availability: {
inStock: boolean;
estimatedDeliveryDays: number;
};
}
const productB: Product = {
id: "102" as ProductID,
name: "Apple iPad",
category: "tablet" as ProductCategory,
price: 599.99,
currency: "GBP" as Currency,
availability: {
inStock: false,
estimatedDeliveryDays: 7,
},
};
currency
가 GBP로 설정되어 있습니다. Currency
타입은 "USD"
, "EUR"
, "JPY"
만 허용합니다.