July 1, 2025

πŸ™ˆ TypeScript `any` νƒ€μž… λ‚¨μš©: νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ“°μ§€ μ•ŠλŠ” 것과 λ‹€λ¦„μ—†λŠ” μ½”λ“œ

TypeScript
μ•„ν‚€ν…μ²˜
Lint/Formatter

Summary

any νƒ€μž…μ€ 단기적인 편의λ₯Ό μ œκ³΅ν•˜μ§€λ§Œ, κ²°κ΅­ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ‚¬μš©μ˜ 의미λ₯Ό ν‡΄μƒ‰μ‹œν‚€κ³  잠재적인 버그λ₯Ό μœ λ°œν•˜λ©° μ½”λ“œ ν’ˆμ§ˆμ„ λ–¨μ–΄λœ¨λ¦½λ‹ˆλ‹€. 적극적인 νƒ€μž… μΆ”λ‘ κ³Ό λͺ…μ‹œμ μΈ νƒ€μž… μ •μ˜, 그리고 μ œλ„€λ¦­ 및 unknown νƒ€μž…μ„ 톡해 νƒ€μž… μ•ˆμ •μ„±μ„ 확보해야 ν•©λ‹ˆλ‹€.

Why Wrong?

TypeScript의 any νƒ€μž…μ€ λͺ¨λ“  μ’…λ₯˜μ˜ 값을 ν—ˆμš©ν•˜μ—¬ μΌμ‹œμ μœΌλ‘œ νŽΈλ¦¬ν•΄ 보일 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ΄λŠ” νƒ€μž… μ‹œμŠ€ν…œμ˜ 핡심 이점인 'νƒ€μž… μ•ˆμ •μ„±'을 μ™„μ „νžˆ μƒμ‹€ν•˜κ²Œ λ§Œλ“­λ‹ˆλ‹€. any νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ TypeScript μ»΄νŒŒμΌλŸ¬κ°€ ν•΄λ‹Ή λ³€μˆ˜μ˜ νƒ€μž…μ„ μ „ν˜€ κ²€μ‚¬ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ, 컴파일 μ‹œμ μ— λ°œκ²¬ν•  수 μžˆμ—ˆλ˜ 잠재적인 버그듀이 λŸ°νƒ€μž„ μ—λŸ¬λ‘œ μ΄μ–΄μ§ˆ κ°€λŠ₯성이 κ·Ήλ„λ‘œ λ†’μ•„μ§‘λ‹ˆλ‹€. 특히 μ™ΈλΆ€ API μ‘λ‹΅μ΄λ‚˜ λ³΅μž‘ν•œ 객체λ₯Ό any둜 μ²˜λ¦¬ν•˜λ©΄, 데이터 ꡬ쑰λ₯Ό μ˜ˆμΈ‘ν•˜κΈ° μ–΄λ €μ›Œμ Έ μ½”λ“œμ˜ 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ΄ 크게 μ €ν•΄λ©λ‹ˆλ‹€. μ΄λŠ” λ¦¬νŒ©ν† λ§μ΄λ‚˜ ν˜‘μ—… μ‹œ 였λ₯˜ λ°œμƒ κ°€λŠ₯성을 높이며, IDE의 μžλ™ μ™„μ„±(IntelliSense) κΈ°λŠ₯ ν™œμš©λ„ λΆˆκ°€λŠ₯ν•˜κ²Œ λ§Œλ“€μ–΄ 개발 생산성을 λ–¨μ–΄λœ¨λ¦½λ‹ˆλ‹€. any νƒ€μž…μ€ νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ‚¬μš©ν•˜λ©΄μ„œλ„ κ·Έ 이점을 ν¬κΈ°ν•˜λŠ” 것과 κ°™μ•„ ν”„λ‘œμ νŠΈμ˜ 견고성을 ν•΄μΉ˜λŠ” 주범이 λ©λ‹ˆλ‹€.

How to Fix?

νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ‚¬μš©ν•˜λŠ” λͺ©μ μ— μΆ©μ‹€ν•˜κ²Œ, 항상 λͺ…μ‹œμ μΈ νƒ€μž… μ •μ˜λ₯Ό μš°μ„ ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ³΅μž‘ν•˜κ±°λ‚˜ λΆˆν™•μ‹€ν•œ 데이터 κ΅¬μ‘°μ—λŠ” interfaceλ‚˜ type aliasλ₯Ό ν™œμš©ν•˜μ—¬ νƒ€μž…μ„ λͺ…ν™•ν•˜κ²Œ μ„ μ–Έν•©λ‹ˆλ‹€. λ‹€μ–‘ν•œ νƒ€μž…μ— μœ μ—°ν•˜κ²Œ λŒ€μ‘ν•˜λ©΄μ„œλ„ νƒ€μž… μ•ˆμ „μ„±μ„ μœ μ§€ν•΄μ•Ό ν•  λ•ŒλŠ” **μ œλ„€λ¦­(Generics)**을 적극 ν™œμš©ν•˜μ—¬ μž¬μ‚¬μš© κ°€λŠ₯ν•˜κ³  νƒ€μž… μ•ˆμ „ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ–΄λ–€ 값이 μ˜¬μ§€ μ•Œ 수 μ—†μ§€λ§Œ μ‚¬μš© μ‹œμ μ— νƒ€μž…μ„ λͺ…ν™•νžˆ ν•˜κ³  μ‹Άλ‹€λ©΄ any λŒ€μ‹  unknown νƒ€μž…μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. unknown은 any처럼 λͺ¨λ“  값을 받을 수 μžˆμ§€λ§Œ, ν•΄λ‹Ή 값을 μ‚¬μš©ν•˜κΈ° 전에 λ°˜λ“œμ‹œ νƒ€μž… 검사(type narrowing)λ₯Ό 톡해 νƒ€μž…μ„ μ’ν˜€μ•Ό ν•˜λ―€λ‘œ λŸ°νƒ€μž„ μ—λŸ¬λ₯Ό λ°©μ§€ν•˜λŠ” 데 훨씬 νš¨κ³Όμ μž…λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ, ν”„λ‘œμ νŠΈ λ‚΄μ—μ„œ any μ‚¬μš©μ„ κ°•μ œν•˜μ§€ μ•ŠκΈ° μœ„ν•΄ @typescript-eslint/no-explicit-any와 같은 ESLint κ·œμΉ™μ„ μ„€μ •ν•˜μ—¬ any μ‚¬μš©μ„ κ²½κ³ ν•˜κ±°λ‚˜ μ•„μ˜ˆ μ—λŸ¬λ‘œ μ²˜λ¦¬ν•˜λŠ” 것이 λͺ¨λ²” μ‚¬λ‘€μž…λ‹ˆλ‹€.

Before Code (Bad)

// 🚨 λ¬Έμ œκ°€ λ˜λŠ” μ½”λ“œ: any νƒ€μž… λ‚¨μš©

// μ™ΈλΆ€ APIμ—μ„œ λ°›μ•„μ˜¨ 데이터λ₯Ό μ²˜λ¦¬ν•˜λŠ” ν•¨μˆ˜ (데이터 ꡬ쑰가 항상 μΌμ •ν•˜μ§€ μ•Šλ‹€κ³  κ°€μ •)
// '일단' any둜 λ°›μ•„μ„œ νƒ€μž… 검사λ₯Ό νšŒν”Όν•©λ‹ˆλ‹€.
function processUserData(data: any) {
  console.log(`User ID: ${data.id}`);
  console.log(`User Name: ${data.name}`);
  
  // data에 'address' 속성이 μ—†κ±°λ‚˜ 'address'κ°€ 객체가 μ•„λ‹ˆλ©΄ λŸ°νƒ€μž„ μ—λŸ¬ λ°œμƒ κ°€λŠ₯μ„±!
  console.log(`User Address: ${data.address.street}`); 

  if (data.status === 'active') {
    // data에 'lastLogin'이 μ—†κ±°λ‚˜ μˆ«μžκ°€ μ•„λ‹ˆλ©΄ Date 객체 생성 및 toLocaleStringμ—μ„œ λŸ°νƒ€μž„ μ—λŸ¬ λ°œμƒ κ°€λŠ₯μ„±!
    console.log(`Last login: ${new Date(data.lastLogin).toLocaleString()}`); 
  }
  return data.id;
}

// μœ νš¨ν•œ 데이터 (이 κ²½μš°λŠ” λ¬Έμ œκ°€ μ—†μ–΄ λ³΄μž…λ‹ˆλ‹€)
processUserData({ 
  id: '123', 
  name: 'Alice', 
  status: 'active', 
  lastLogin: 1678886400000, 
  address: { street: 'Main St', city: 'Anytown' }
});

// ✨ ν•˜μ§€λ§Œ, ν•„λ“œκ°€ λˆ„λ½λ˜κ±°λ‚˜ νƒ€μž…μ΄ λ‹€λ₯Έ 데이터가 듀어와도 any λ•Œλ¬Έμ— 컴파일 μ‹œμ μ— 였λ₯˜λ₯Ό κ°μ§€ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.
processUserData({ id: '456', username: 'Bob', status: 'inactive' }); // 'name', 'lastLogin', 'address' ν•„λ“œ μ—†μŒ
processUserData({ id: '789', name: 'Charlie', status: 'pending', lastLogin: 'yesterday' }); // 'lastLogin'이 string

// μœ„ ν˜ΈμΆœλ“€μ€ λŸ°νƒ€μž„μ— μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚€κ±°λ‚˜ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ™μž‘μ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

After Code (Good)

// ✨ κ°œμ„ λœ μ½”λ“œ: λͺ…μ‹œμ μΈ νƒ€μž…κ³Ό unknown, μ œλ„€λ¦­ ν™œμš©

interface UserProfile {
  id: string;
  name: string;
  email?: string; // 선택적 속성
  status: 'active' | 'inactive' | 'pending';
  lastLogin?: number; // νƒ€μž„μŠ€νƒ¬ν”„ (선택적)
  address?: { // μ£Όμ†Œλ„ 선택적, λ‚΄λΆ€ ꡬ쑰 λͺ…μ‹œ
    street: string;
    city: string;
  }
}

// λͺ…ν™•ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ—¬ νƒ€μž… μ•ˆμ •μ„± 확보
function processStrictUserData(data: UserProfile): string {
  console.log(`User ID: ${data.id}`);
  console.log(`User Name: ${data.name}`);
  
  // μ˜΅μ…”λ„ 체이닝과 νƒ€μž… κ°€λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ•ˆμ „ν•˜κ²Œ μ ‘κ·Όν•©λ‹ˆλ‹€.
  if (data.address?.street) {
    console.log(`User Address: ${data.address.street}`);
  }

  if (data.status === 'active' && data.lastLogin) {
    // lastLogin이 number둜 보μž₯λ˜λ―€λ‘œ μ•ˆμ „ν•˜κ²Œ μ‚¬μš©ν•©λ‹ˆλ‹€.
    console.log(`Last login: ${new Date(data.lastLogin).toLocaleString()}`);
  }
  return data.id;
}

// ✨ 이제 컴파일 μ‹œμ μ— νƒ€μž… μ—λŸ¬λ₯Ό κ°μ§€ν•©λ‹ˆλ‹€!
processStrictUserData({ 
  id: '123', 
  name: 'Alice', 
  status: 'active', 
  lastLogin: 1678886400000, 
  address: { street: 'Main St', city: 'Anytown' }
});

// Error: Argument of type '{ id: string; username: string; status: string; }' is not assignable to parameter of type 'UserProfile'.
// Object literal may only specify known properties, and 'username' does not exist in type 'UserProfile'.
// processStrictUserData({ id: '456', username: 'Bob', status: 'inactive' }); 

// unknown νƒ€μž…μ„ μ‚¬μš©ν•˜μ—¬ 더 μ•ˆμ „ν•œ 데이터 처리 (μ™ΈλΆ€μ—μ„œ μ˜€λŠ” λΆˆν™•μ‹€ν•œ 데이터에 적합)
function processUnknownUserData(rawData: unknown) {
  // rawDataκ°€ UserProfile νƒ€μž…μΈμ§€ λŸ°νƒ€μž„μ— μ—„κ²©ν•˜κ²Œ κ²€μ¦ν•©λ‹ˆλ‹€.
  if (
    typeof rawData === 'object' && rawData !== null &&
    'id' in rawData && typeof rawData.id === 'string' &&
    'name' in rawData && typeof rawData.name === 'string' &&
    'status' in rawData && (rawData.status === 'active' || rawData.status === 'inactive' || rawData.status === 'pending')
  ) {
    const userData: UserProfile = rawData as UserProfile; // νƒ€μž… κ°€λ“œλ₯Ό ν†΅κ³Όν–ˆμœΌλ―€λ‘œ μ•ˆμ „ν•˜κ²Œ 단언
    console.log(`Processing unknown user: ${userData.name}, Status: ${userData.status}`);
  } else {
    console.warn('Received invalid user data format or missing required fields.');
  }
}

processUnknownUserData({ id: '111', name: 'David', status: 'active' });
processUnknownUserData({ userId: '222', userName: 'Eve' }); // 'id'와 'name', 'status' ν•„λ“œ 검증에 μ‹€νŒ¨ν•˜μ—¬ κ²½κ³  λ©”μ‹œμ§€ 좜λ ₯

// μ œλ„€λ¦­ ν™œμš© μ˜ˆμ‹œ (더 μœ μ—°ν•˜κ²Œ νƒ€μž… μ•ˆμ „ν•œ ν•¨μˆ˜ μž‘μ„±)
interface ApiResponse<T> {
  statusCode: number;
  message: string;
  data: T;
}

function handleApiResponse<T>(response: ApiResponse<T>): T {
  if (response.statusCode === 200) {
    return response.data; 
  } else {
    throw new Error(response.message);
  }
}

interface Product {
  productId: string;
  productName: string;
  price: number;
}

const productResponse: ApiResponse<Product> = {
  statusCode: 200,
  message: 'Success',
  data: { productId: 'P001', productName: 'Laptop', price: 1200 }
};

const product = handleApiResponse(productResponse);
console.log(product.productName); // 'product'λŠ” Product νƒ€μž…μœΌλ‘œ μΆ”λ‘ λ©λ‹ˆλ‹€.

// const errorResponse: ApiResponse<Product> = {
//   statusCode: 404,
//   message: 'Product not found',
//   data: { productId: 'P002', productName: 'Monitor', price: 300 } // Tκ°€ Productμ΄λ―€λ‘œ dataλŠ” Product νƒ€μž…μ΄μ–΄μ•Ό 함
// };
// try {
//   handleApiResponse(errorResponse);
// } catch (e) {
//   console.error(e.message);
// }

You Might Also Like

`any` νƒ€μž…μ„ ν”Όν•˜κ³  기본적인 νƒ€μž… μΆ”λ‘  및 λͺ…μ‹œμ μΈ νƒ€μž… μ„ μ–Έμ˜ μ€‘μš”μ„±μ„ μ΄ν•΄ν•˜λŠ” 데 도움이 λ©λ‹ˆλ‹€. TypeScript의 핡심 이점인 νƒ€μž… μ•ˆμ „μ„±μ„ ν™•λ³΄ν•˜κΈ° μœ„ν•œ μ‹œμž‘μ μž…λ‹ˆλ‹€.
https://www.typescriptlang.org/docs/handbook/2/everyday-types.html
`any` λŒ€μ‹  λ‹€μ–‘ν•œ νƒ€μž…μ— μœ μ—°ν•˜κ²Œ λŒ€μ‘ν•˜λ©΄μ„œλ„ νƒ€μž… μ•ˆμ „μ„±μ„ μœ μ§€ν•  수 μžˆλŠ” μ œλ„€λ¦­μ˜ κ°œλ…κ³Ό μ‹€μ œ μ‚¬μš©λ²•μ„ ν•™μŠ΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” `any`λ₯Ό λŒ€μ²΄ν•˜λŠ” κ°•λ ₯ν•œ νŒ¨ν„΄μž…λ‹ˆλ‹€.
https://www.typescriptlang.org/docs/handbook/2/generics.html
`any`와 μœ μ‚¬ν•˜κ²Œ λͺ¨λ“  값을 받을 수 μžˆμ§€λ§Œ, μ‚¬μš© 전에 νƒ€μž…μ„ λͺ…ν™•νžˆ μ’νžˆλ„λ‘ κ°•μ œν•˜μ—¬ `any`보닀 훨씬 μ•ˆμ „ν•œ λŒ€μ•ˆμΈ `unknown` νƒ€μž…μ— λŒ€ν•΄ μžμ„Ένžˆ μ„€λͺ…ν•©λ‹ˆλ‹€. μ™ΈλΆ€ 데이터 처리 μ‹œ μœ μš©ν•˜κ²Œ μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.
https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown