vanillinav 2024. 10. 9. 20:38
728x90
반응형

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;

위 예시에서 inputValueunknown 타입으로 선언되어 있으며, 사용자가 입력한 값이 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;

이 코드에서 userDataunknown 타입으로 선언되어 있습니다. API로부터 데이터를 받아온 후, 그 데이터가 User 타입인지 확인하는 과정을 거친 후에만 렌더링합니다. 만약 데이터가 예상한 구조가 아니라면 안전하게 잘못된 데이터를 처리할 수 있습니다.

4. typeofinstanceof를 사용한 타입 좁히기

unknown 타입의 값을 사용할 때, 일반적으로 typeofinstanceof를 사용하여 값을 안전하게 좁히는 방법이 있습니다.

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

728x90
반응형