[101] 타입스크립트의 unknown 타입
TypeScript에서 unknown
타입은 any
와 혼동될 수 있지만, 코드 안전성에 있어 결정적인 차이를 만듭니다. unknown
을 효과적으로 사용하면 React 애플리케이션의 안정성과 오류 방지를 크게 향상시킬 수 있습니다. 이 글에서는 unknown
타입을 활용한 안전한 타입 검사 기법을 React 애플리케이션을 중심으로 자세히 살펴봅니다.
1. unknown
타입의 기본 개념
unknown
타입은 말 그대로 "알 수 없는" 타입을 나타냅니다. 어떤 값이 올지 확실하지 않을 때 unknown
을 사용할 수 있으며, 이는 값을 사용하기 전에 반드시 그 타입을 확인해야 한다는 특징이 있습니다.
let unknownValue: unknown;
unknownValue = 100; // 숫자 할당 가능
unknownValue = "hello world"; // 문자열 할당 가능
unknownValue = () => console.log("this is a function"); // 함수 할당 가능
unknown
타입은 어떤 값이든 할당할 수 있습니다. 그러나 중요한 것은 이 값을 사용할 때는 타입 검사를 반드시 거쳐야 한다는 점입니다. 즉, unknown
변수에 직접 메서드를 호출하거나 프로퍼티에 접근할 수 없습니다.
let unknownValue: unknown = "hello";
console.log(unknownValue.toUpperCase()); // 컴파일 에러!
이 코드에서는 unknownValue
가 문자열인지 확인하지 않았기 때문에 에러가 발생합니다.
2. unknown
타입의 활용
React 애플리케이션에서 사용자 입력 또는 외부 API 데이터를 처리할 때는 그 값의 타입을 미리 알 수 없는 경우가 많습니다. 이때 unknown
타입을 사용하여 타입 검사를 강제하면 오류를 줄일 수 있습니다.
사용자 입력을 처리하는 예시
다음 예시는 React에서 사용자 입력을 처리하는 컴포넌트입니다. 사용자가 입력한 값의 타입이 명확하지 않은 경우, 우리는 이를 unknown
으로 처리한 후 안전하게 타입을 좁힐 수 있습니다.
import React, { useState } from 'react';
const UserInputComponent: React.FC = () => {
const [inputValue, setInputValue] = useState<unknown>("");
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value: unknown = event.target.value;
setInputValue(value);
};
const handleSubmit = () => {
if (typeof inputValue === 'string') {
console.log('사용자 입력은 문자열입니다:', inputValue.toUpperCase());
} else {
console.log('잘못된 입력 유형입니다.');
}
};
return (
<div>
<label htmlFor="userInput" >입력 내용을 적어주세요:</label>
<input
type="text"
id="userInput"
onChange={handleInputChange}
aria-label="사용자 입력 필드"
/>
<button
onClick={handleSubmit}
aria-label="입력 내용 제출"
>
제출
</button>
</div>
);
};
export default UserInputComponent;
위 예시에서 inputValue
는 unknown
타입으로 선언되어 있으며, 사용자가 입력한 값이 string
타입인지 확인한 후에만 문자열 관련 작업(예: 대문자 변환)을 수행할 수 있습니다. 이렇게 unknown
타입을 사용함으로써 타입 오류를 방지할 수 있습니다.
3. 외부 API 데이터 처리에서의 unknown
타입
외부 API에서 데이터를 받아오는 상황에서는 반환되는 데이터의 타입을 정확히 알 수 없는 경우가 있습니다. 이때 unknown
타입을 사용하여 안전하게 데이터를 처리할 수 있습니다.
API 응답 데이터 처리 예시
import React, { useEffect, useState } from 'react';
type User = {
id: number;
name: string;
};
const FetchUserComponent: React.FC = () => {
const [userData, setUserData] = useState<unknown | null>(null); // null을 명시적으로 포함
const [error, setError] = useState<string | null>(null); // 에러 메시지를 위한 상태 추가
useEffect(() => {
fetch('https://api.example.com/user/1')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data && typeof data === 'object' && 'id' in data && 'name' in data) {
setUserData(data as User); // 타입 가드 후 캐스팅
} else {
setError('잘못된 사용자 데이터 형식입니다.'); // 에러 메시지 설정
}
})
.catch(error => {
setError(`사용자 데이터를 가져오는 중 오류가 발생했습니다: ${error.message}`); // 에러 메시지 설정
});
}, []);
return (
<div>
<h1>불러온 사용자 데이터</h1>
{error ? (
<p role="alert" aria-live="assertive">
오류: {error}
</p>
) : userData ? (
<div>
<h2>사용자 이름: <span id="userName">{userData.name}</span></h2>
<p id="userId" aria-hidden="true">사용자 ID: {userData.id}</p>
</div>
) : (
<p>데이터를 불러오는 중입니다...</p>
)}
</div>
);
};
export default FetchUserComponent;
이 코드에서 userData
는 unknown
타입으로 선언되어 있습니다. API로부터 데이터를 받아온 후, 그 데이터가 User
타입인지 확인하는 과정을 거친 후에만 렌더링합니다. 만약 데이터가 예상한 구조가 아니라면 안전하게 잘못된 데이터를 처리할 수 있습니다.
4. typeof
와 instanceof
를 사용한 타입 좁히기
unknown
타입의 값을 사용할 때, 일반적으로 typeof
나 instanceof
를 사용하여 값을 안전하게 좁히는 방법이 있습니다.
typeof
를 사용한 타입 좁히기 예시
"타입 좁히기" (Type Narrowing)는 TypeScript에서 특정 변수나 표현식의 타입을 더욱 구체적인 타입으로 제한하는 것을 의미합니다. 처음에는 넓은 범위의 타입 (예: `unknown`, `any`, `object`)으로 선언된 변수가 있더라도, 런타임이나 컴파일 타임에 특정 조건을 통해 그 타입을 더욱 정확하게 특정할 수 있습니다. 이를 통해 타입스크립트의 타입 안전성을 유지하면서 더욱 다양한 코드를 작성할 수 있습니다.
function handleUnknownInput(input: unknown): string {
if (typeof input === 'string') {
return input.toUpperCase(); // 문자열로 안전하게 변환 후 사용 가능
} else if (typeof input === 'number') {
return input.toString(); // 숫자일 경우 문자열로 변환
} else {
return 'Unknown input type';
}
}
instanceof
를 사용한 클래스 타입 확인 예시
class Admin extends Person {
adminLevel: number;
constructor(name: string, adminLevel: number) {
super(name);
this.adminLevel = adminLevel;
}
adminAction(): void {
console.log(`${this.name} (Admin Level ${this.adminLevel}): 관리자 작업 수행`);
}
}
class RegularUser extends Person {
constructor(name: string) {
super(name);
}
userAction(): void {
console.log(`${this.name}: 일반 사용자 작업 수행`);
}
}
function handleUser(user: unknown): void {
if (user instanceof Admin) {
user.adminAction(); // 관리자 권한 작업 수행
} else if (user instanceof RegularUser) {
user.userAction(); // 일반 사용자 작업 수행
} else if (typeof user === 'object' && user !== null && 'name' in user && typeof (user as any).name === 'string') {
console.log(`알 수 없는 사용자 ${ (user as any).name }`);
} else {
console.log("유효하지 않은 사용자 데이터입니다.");
}
}
let admin1: unknown = new Admin("Jane", 5);
let user1: unknown = new RegularUser("Mike");
let unknownUser: unknown = { name: "Unknown", age: 30 }; // 이름만 있는 객체
let invalidUser: unknown = 123; // 숫자
handleUser(admin1); // 관리자 권한 작업 수행
handleUser(user1); // 일반 사용자 작업 수행
handleUser(unknownUser); // 알 수 없는 사용자 Unknown
handleUser(invalidUser); // 유효하지 않은 사용자 데이터입니다.
5. in
연산자를 사용한 객체 프로퍼티 확인
객체 타입을 확인할 때는 in
연산자를 사용하여 프로퍼티의 존재 여부를 확인할 수 있습니다.
in
연산자 예시
interface Product {
id: number;
name: string;
price: number;
description?: string; // 설명은 선택적일 수 있습니다.
discount?: number; // 할인율도 선택적입니다.
}
function displayProductInfo(product: unknown): void {
if (typeof product === 'object' && product !== null && 'id' in product && 'name' in product && 'price' in product) {
// 타입 가드: 필수 프로퍼티('id', 'name', 'price')가 있는지 확인합니다.
const productInfo = product as Product; // 타입 단언
console.log(`상품 ID: ${productInfo.id}`);
console.log(`상품 이름: ${productInfo.name}`);
console.log(`상품 가격: ${productInfo.price}`);
if ('description' in productInfo) {
console.log(`상품 설명: ${productInfo.description}`);
}
if ('discount' in productInfo) {
console.log(`할인율: ${productInfo.discount}%`);
}
} else {
console.log("잘못된 상품 데이터입니다.");
}
}
// 다양한 형태의 상품 데이터를 테스트합니다.
const product1: unknown = { id: 1, name: "티셔츠", price: 20000, description: "멋진 티셔츠입니다." };
const product2: unknown = { id: 2, name: "바지", price: 30000, discount: 10 };
const product3: unknown = { id: 3, name: "신발", price: 50000 };
const product4: unknown = { id: 4, name: "모자" }; // price 프로퍼티가 없음
const product5: unknown = "잘못된 데이터"; // 객체가 아님
const product6: unknown = null; // null 값
displayProductInfo(product1);
displayProductInfo(product2);
displayProductInfo(product3);
displayProductInfo(product4);
displayProductInfo(product5);
displayProductInfo(product6);
6. 결론
본 글에서는 TypeScript의 unknown 타입을 React 애플리케이션에서 안전하게 활용하는 방법을 살펴보았습니다. unknown 타입은 any와 달리 타입 안전성을 보장하며, 사용자 입력, 외부 API 데이터 처리 등에서 발생할 수 있는 타입 관련 오류를 효과적으로 예방합니다. typeof, instanceof, in 연산자를 활용한 타입 검사 기법을 통해 더욱 안정적이고 예측 가능한 코드를 작성할 수 있습니다.
7. 참조
- 우아한 타입스크립트 with 리액트: 3.1 타입스크립트만의 독자적인 타입 시스템
- 타입스크립트 핸드북: https://www.typescriptlang.org/docs/handbook/intro.html