[TypeScript] React에서 자주 사용되는 작업 패턴

우지끈·2024년 12월 10일
post-thumbnail

타입 단언(Type Assertion)

에디터가 추론하지 못하는 타입을 알고 있을 때 as 키워드를 통해 에디터에게 타입을 알려줄 수 있다.
!는 non-null assertion 연산자로 null 타입이 아님을 단언한다.
그러나 타입 단언은 가능하면 지양하는 것이 좋다. 타입 지정을 꼼꼼히 해줄 수록 타입 단언이 필요한 경우는 줄어들 것이다.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
// 타입스크립트에서 ! 는 non-null assertion 으로 null 타입이 아님을 의미
// ! 대신 as 키워드로도 타입단언 가능 // as HTMLDivElement
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);

API response에 대한 타입 지정

import axios from "axios";

// Person 에 대한 데이터 타입 지정은
// api 문서를 살펴보거나 api 호출 이후 콘솔로그 또는 네트워크창을 통해 확인한 뒤 지정
type Person = { id: number; age: number; height: number };

async function getPerson(): Promise<Person[]> {
  const response = await fetch(`http://localhost:4000/people`);
  const data: Person[] = await response.json();
  return data;
}

getPerson().then((data) => console.log(data));
{
  "people": [
    {
      "id": 1,
      "age": 10,
      "height": 175
    },
    {
      "id": 2,
      "age": 20,
      "height": 165
    },
    {
      "id": 3,
      "age": 30,
      "height": 185
    }
  ]
}

인덱스 시그니처 (index signature)

객체 타입의 모든 프로퍼티가 특정 타입의 키와 특정 타입의 값을 가져야 한다고 명시하는 구문
-> 인덱스 시그니처로 정의된 객체 타입의 경우 빈 객체도 유효하게 동작

type StringMap = {
  [key: string]: string;
};

const example1: StringMap = {}; // 빈 객체, 유효함
const example2: StringMap = { key1: "value1", key2: "value2" }; // 여러 개의 키-값 쌍, 유효함

console.log(example1); // 출력: {}
console.log(example2); // 출력: { key1: 'value1', key2: 'value2' }

useState에 대한 타입 지정

useState<T>에서 T는 상태 데이터의 타입을 의미한다.

const [num, setNum] = useState<number>(10);
const [str, setStr] = useState<string>("");

type Todo = {
	id: string;
	title: string;
	content: string;
	isDone: boolean;
	createdAt: number;
};
const [todos, setTodos] = useState<Todo[]>;

useState를 쓸 때 가급적 명시적으로 상태를 지정하는 습관을 들이는 것이 좋으며, 특히 객체 타입의 상태를 관리할 때는 명시적 타입 지정이 필요하다.

import React, { useState } from 'react';

type User = {
  id: number;
  name: string;
};

const MyComponent = () => {
  const [user, setUser] = useState<User | null>(null);

  const handleLogin = () => {
    // 예제 사용자 데이터
    const userData: User = {
      id: 1,
      name: 'John Doe'
    };

    setUser(userData);
  };

  return (
    <div>
      {user ? <p>Welcome, {user.name}</p> : <p>Please log in</p>}
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};

export default MyComponent;

위 예시 코드에서 타입 지정을 하지 않은 채로 초기값만 null로 지정해뒀다면, typescript는 user의 상태를 null로 추론하기에 객체를 할당하려 할 때 타입 에러가 발생할 것이다.

const [settings, setSettings] = useState({ theme: "dark", notifications: true });

setSettings({ thme: "light" }); // 잘못된 속성 이름 (런타임 에러 발생 가능)

또한, 타입을 명시하지 않으면 추후에 잘못된 속성 이름이나 값의 타입이 컴파일 단계에서 잡히지 않을 수도 있기에 타입을 꼭 명시적으로 지정해둬야 한다.


제네릭 타입 (Generic Type)

이전 글에서도 다룬 적이 있지만, 제네릭은 일종의 타입 매개변수로 함수 타입을 정의할 때 다양한 타입을 다룰 수 있도록 해준다.

제네릭은 이름 바로 오른쪽에 사용 선언하면 된다.

// 제네릭을 사용하는 타입 별칭
type FlexibleItemType<T> = {
	item: T;
}
const stringItem: FlexibleItemType<string> = {item: "Hello"};

// 제네릭을 사용하는 인터페이스
interface FlexibleItemInterface<T> {
	item: T;
}
const numberItem: FlexibleItemInterface<number> = {item : 42};

// 제네릭을 사용하는 함수 선언문
function getItem<T>(item: T): T {
	return item;
}
const stringResult = getItem<string>("Hello");
// 명시적으로 제네릭타입을 지정하지 않으면 타입추론이 된다.
const bolleanResult = getItem(trun);

// 제네릭을 사용하는 화살표 함수 -> 매개변수 자리 바로 왼쪽에 제네릭 선언
const getItemArrow = <T>(item: T) : T => {
	return item;
}
const numberResult = getItemArrow<number>(123);

event에 대한 타입 지정

event 타입 확인은 onChange에 할당된 이벤트핸들러의 매개변수에 해당하는 event에 마우스오버 시 확인할 수 있다.

import { useState } from 'react';

const MyComponent = () => {
  const [inputValue, setInputValue] = useState<string>('');

  // 이벤트 매개변수에 타입을 지정. 또는 handleChange 자체에 함수 타입을 지정해줄 수도 있다.
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
    console.log(event.target.value);
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
      <p>Current Value: {inputValue}</p>
    </div>
  );
};

export default MyComponent;

인덱스 접근 타입 (indexed access type)

객체 타입의 특정 속성 타입을 동적으로 추출할 수 있는 기능이다. 이를 통해 코드의 타입 안정성을 높이고 중복 정의를 방지할 수 있다.

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];

위 코드에서 Person["age"]는 Person 타입의 age 속성의 타입(number)을 추출한다.
타입 추론을 통해 type Age = number로 변환된다.


인터섹션 타입 (A & B)

타입 A와 타입 B의 모든 조건을 만족시키는 타입 -> 두 타입의 속성을 모두 포함해여 새 타입을 만든다고 생각하면 된다!

type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: number;
  department: string;
};

// 인터섹션 타입을 사용하여 새로운 타입 생성
type PersonEmployee = Person & Employee;

const personEmployee: PersonEmployee = {
  name: "Alice",
  age: 30,
  employeeId: 12345,
  department: "Engineering"
};

console.log(personEmployee);

0개의 댓글