8 분 소요


React에서 TS 사용하기 공식 문서↗️

1. 함수형 컴포넌트 타입 지정: React.FC

React.FC(React.FunctionComponent)는 TypeScript와 React를 사용할 때 함수형 컴포넌트를 정의하는 데 사용되는 타입이다.

import React from "react";

const App: React.FC = () => {
  return <div>App</div>;
};

export default App;



2. Props 타입 지정

2.1 Props를 위한 타입 정의하기

아래와 같은 객체를 props로 다른 컴포넌트에게 전달하려면 어떻게 해야할까?

// 아래 코드는 오류 발생
import React from "react";
import PostsPage from "./PostsPage";

const postInfo = [
  {
    postId: 1,
    title: "너무 더워요",
    body: "오늘 날씨 30도☀️",
    date: "2024-09-18",
    tags: ["날씨", "여름"],
    user: {
      userId: 2,
      username: "시은",
    },
  },
];

const App: React.FC = () => {
  return (
    <div>
      <PostsPage postInfo={postInfo} />
    </div>
  );
};

export default App;


객체를 props로 다른 컴포넌트에 전달할 때, 먼저 해당 객체에 대한 타입을 정의해야 한다.

  • 타입을 만들기 위해 src/model 이라는 폴더 안에 posts.ts 라는 파일을 생성하자.
  • 타입을 생성하는 방법에는 type, interface 으로 2가지 방법이 있다.
  • type은 잠재적인 오류의 위험이 있으므로 되도록이면 interface를 사용하고, 부득이한 상황(타입 앨리어싱)에만 type 을 써주도록 하자[참고할만한 글 ↗️]
// src/model/posts.ts

export interface Post {
  postId: number;
  title: string;
  body: string;
  date: string;
  tage: string[];
  user: {
    userId: number;
    username: string;
  };
}

아래와 같이 type 내에 다른 타입을 포함시킬 수도 있다. (추천)

// src/model/posts.ts

export interface Post {
  postId: number;
  title: string;
  body: string;
  date: string;
  tags: string[];
  user: User;
}

export interface User {
  userId: number;
  username: string;
}


2.2 컴포넌트에 타입 적용하기

위와 같이 정의한 타입을 컴포넌트에 적용하면 된다.

// src/App.tsx

import React from "react";
import PostsPage from "./PostsPage";
import { Post } from "./model/posts";

const postInfo: Post[] = [
  {
    postId: 1,
    title: "너무 더워요",
    body: "오늘 날씨 30도☀️",
    date: "2024-09-18",
    tags: ["날씨", "여름"],
    user: {
      userId: 2,
      username: "시은",
    },
  },
];

const App: React.FC = () => {
  return (
    <div>
      <PostsPage postInfo={postInfo} />
    </div>
  );
};

export default App;

2.3 Props 타입 지정하기

컴포넌트에 전달할 props의 타입을 정의하자.

// src/PostsPage.tsx

import React from "react";
import { Post } from "./model/posts";

// props의 경우 배열을 받을 수 있도록 타입 수정
interface PostsProps {
  postInfo: Post[];
}

const PostsPage: React.FC<PostsProps> = ({ postInfo }) => {
  return (
    <ul>
      {postInfo.map((post) => (
        <li key={post.postId}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
          <p>{post.date}</p>
          <p>작성자: {post.user.username}</p>
        </li>
      ))}
    </ul>
  );
};

export default PostsPage;


interface이 아닌, type로 type을 만드는 경우 아래와 같이 하면 된다.

type PostsProps = {
  postInfo: Post[];
};



3. Generic  간단히 이해하기

제네릭(generic)이란 데이터의 타입을 일반화한다(generalize)하는 방법을 의미한다.

간단히 데이터의 타입을 변수화 하는 것이다! 아래와 같이 사용한다.

3.1 제네릭 타입 선언

type Generic<T> = {
  someValue: T;
};

type Test = Generic<string>;


3.2 제네릭 함수 호출

function someFunc<T>(value: T) {}
someFunc<string>("hello");

즉, 타입을 생성할 때 원하는 타입을 인자로 받아 넣어주면 된다.



4. useState에서 TS 사용하기

useState에 제네릭 문법을 사용하여 타입을 지정한다.

  • useState<type> 와 같이 사용하면 된다. <>는 제네릭 문법으로 useState를 부르는 순간 type을 지정해준다.
  • 원시 값(number, string)은 타입 추론이 되기 때문에 타입 지정을 안 해줘도 되지만, 참조 값(객체, 배열)은 어떠한 아이템이 담겼는지 알 수 없기 때문에 추론이 불가능하여 타입을 직접 지정해줘야 한다.
// App.tsx

import React, { useState } from "react";
import PostsPage from "./PostsPage";
import { Post } from "./model/posts";

const postInfo: Post[] = [
  {
    postId: 1,
    title: "너무 더워요",
    body: "오늘 날씨 30도☀️",
    date: "2024-09-18",
    tags: ["날씨", "여름"],
    user: {
      userId: 2,
      username: "시은",
    },
  },
];

const App: React.FC = () => {
  // useState에 제네릭 문법을 사용하여 타입을 지정
  const [myPosts, setMyPosts] = useState<Post[]>(postInfo);

  return (
    <div>
      <PostsPage postInfo={myPosts} />
    </div>
  );
};

export default App;



5. 함수 타입 지정하기

컴포넌트에 전달할 함수의 타입을 정의한다.

// src/App.tsx

import React, { useState } from "react";
import PostsPage from "./PostsPage";
import { Post } from "./model/posts";

const postInfo: Post[] = [
  {
    postId: 1,
    title: "너무 더워요",
    body: "오늘 날씨 30도☀️",
    date: "2024-09-18",
    tags: ["날씨", "여름"],
    user: {
      userId: 2,
      username: "시은",
    },
  },
];

const App: React.FC = () => {
  const [myPosts, setMyPosts] = useState<Post[]>(postInfo);

  // 포스트 배열에서 특정 포스트의 title을 변경하는 함수
  const changeTitle = (id: number, newTitle: string) => {
    const updatedPosts = myPosts.map((post) =>
      post.postId === id ? { ...post, title: newTitle } : post
    );
    setMyPosts(updatedPosts); // 변경된 배열을 setMyPosts로 업데이트
  };

  return (
    <div>
      <PostsPage postInfo={myPosts} changeTitle={changeTitle} />
    </div>
  );
};

export default App;


void는 TypeScript에서 반환 값이 없는 함수를 나타내는 타입이다.

// src/PostsPage.tsx

import React, { useState } from "react";
import { Post } from "./model/posts";

interface PostsProps {
  postInfo: Post[];
  changeTitle(id: number, newTitle: string): void;
}

const PostsPage: React.FC<PostsProps> = ({ postInfo, changeTitle }) => {
  const [newTitle, setNewTitle] = useState<string>("");

  return (
    <ul>
      {postInfo.map((post) => (
        <li key={post.postId}>
          <h2>{post.title}</h2>
          <p>{post.body}</p>
          <p>{post.date}</p>
          <p>작성자: {post.user.username}</p>
        </li>
      ))}
    </ul>
  );
};

export default PostsPage;



6. Event Handler 사용하기

이벤트 핸들러에서 TS를 사용할 때는 event에 대한 type을 지정해야한다.

따라서, 이벤트 핸들러를 인라인으로 작성하고 e에 마우스를 올려보면 어떤 type을 썼는지 타입 추론이 가능하다.

return <div onChange={(e) => {}}>{}</div>;


type 복사 붙여넣기!

const TodoInput = () => {
  const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };
  return (
    <section>
      <form onSubmit={handleOnSubmit}>
        <input type="text" name="title" />
        <input type="text" name="content" />

        <button type="submit">제출</button>
      </form>
    </section>
  );
};

export default TodoInput;



7. interface extends 사용하기

interface extends는 TypeScript에서 인터페이스를 확장할 때 사용하는 구문이다.

이를 통해 기존 인터페이스의 속성이나 메서드를 유지하면서도 추가적인 속성을 정의할 수 있다.


아래 User 타입에 새로운 타입을 extends로 확장해보자.

// src/model/posts.ts

export interface Post {
  postId: number;
  title: string;
  body: string;
  date: string;
  tags: string[];
  user: User;
}

export interface User {
  userId: number;
  username: string;
}


이제 UserPage 컴포넌트에 확장된 UserProps 타입을 적용하자.

User 인터페이스를 확장한 UserProps에 추가 속성 email, lastLogin, isActive, 그리고 메서드 showUserName을 정의하고 UserPage에서 props로 받아 사용 한다.

// src/UserPage.tsx

import React from "react";
import { User } from "./model/posts";

interface UserProps extends User {
  email: string;
  lastLogin?: string;
  isActive: boolean;
  showUserName(name: string): string;
}

const UserPage: React.FC<UserProps> = ({
  userId,
  username,
  email,
  lastLogin,
  isActive,
  showUserName,
}) => {
  return (
    <div>
      <h2>User Information</h2>
      <p>User ID: {userId}</p>
      <p>Username: {username}</p>
      <p>Email: {email}</p>
      <p>Last Login: {lastLogin || "로그인 정보 없음"}</p>
      <p>Account Status: {isActive ? "Active" : "Inactive"}</p>
    </div>
  );
};

export default UserPage;


마지막으로 App 컴포넌트에서 확장된 UserProps을 적용하자.

App 컴포넌트에서 UserPage에 확장된 속성들을 props로 전달하면 된다.

// src/App.tsx

import React, { useState } from "react";
import PostsPage from "./PostsPage";
import { Post } from "./model/posts";
import UserPage from "./UserPage";

const postInfo: Post[] = [
  {
    postId: 1,
    title: "너무 더워요",
    body: "오늘 날씨 30도☀️",
    date: "2024-09-18",
    tags: ["날씨", "여름"],
    user: {
      userId: 2,
      username: "시은",
    },
  },
];

const App: React.FC = () => {
  const [myPosts, setMyPosts] = useState<Post[]>(postInfo);

  const changeTitle = (id: number, newTitle: string) => {
    const updatedPosts = myPosts.map((post) =>
      post.postId === id ? { ...post, title: newTitle } : post
    );
    setMyPosts(updatedPosts);
  };

  const showUserName = (userName: string) => {
    return userName;
  };

  return (
    <div>
      <PostsPage postInfo={myPosts} changeTitle={changeTitle} />
      <UserPage
        userId={3}
        username="시은"
        email="sieun@example.com"
        isActive={true}
        showUserName={showUserName}
      />
    </div>
  );
};

export default App;



7. Omit과 Pick

7.1 Omit

Omit<T, K>는 타입 T에서 속성 K를 제외한 새로운 타입을 생성하는 TypeScript 유틸리티 타입이다.

// src/UserPage.tsx

import React from "react";
import { User } from "./model/posts";

// User 인터페이스에서 `userId`를 제외하고 새로운 속성을 추가한 `UserProps` 타입을 정의
interface UserProps extends Omit<User, "userId"> {
  email: string;
  lastLogin?: string;
  isActive: boolean;
  showUserName(name: string): string;
}

const UserPage: React.FC<UserProps> = ({
  // userId는 Omit으로 제외됨
  username,
  email,
  lastLogin,
  isActive,
  showUserName,
}) => {
  return (
    <div>
      <h2>User Information</h2>
      {/* userId는 Omit으로 제외되어 사용하지 않음 */}
      <p>Username: {username}</p>
      <p>Email: {email}</p>
      <p>Last Login: {lastLogin || "로그인 정보 없음"}</p>
      <p>Account Status: {isActive ? "Active" : "Inactive"}</p>
    </div>
  );
};

export default UserPage;


7.2 Pick

Pick<T, K>는 타입 T에서 속성 K만 선택하여 새로운 타입을 생성하는 TypeScript 유틸리티 타입이다.

// src/PostsPage.tsx

import React from "react";
import { Post } from "./model/posts";

//  `Post` 타입에서 `postId`만 선택한 새로운 타입을 정의
export type PostsProps = {
  postInfo: Pick<Post, "postId">[]; // postId만 사용한 배열
};

const PostsPage: React.FC<PostsProps> = ({ postInfo }) => {
  return (
    <ul>
      {postInfo.map((post) => (
        <li key={post.postId}>
          <p>Post ID: {post.postId}</p>
        </li>
      ))}
    </ul>
  );
};

export default PostsPage;


Pick을 사용한 useState로 선택된 속성 관리

editTodo는 선택된 두 가지 속성(title, content)만 포함하는 상태로 관리되기 때문에, 편집 기능을 구현할 때 유용하다.

// type/todo.type.ts

export type EditTodo = Pick<Todo, "title" | "content">;
// TodoItem.tsx

const [editTodo, setEditTodo] = useState<EditTodo | null>(null);



8. api에서 TS 사용하기

API 응답 타입을 제네릭으로 정의한다.

제네릭을 사용하면 다양한 타입에 대해 재사용 가능한 코드를 작성할 수 있다.

// src/model/api.ts

export type ApiResponse<T> = {
  data: T[];
  totalPage: number;
  page: number;
};
  • T는 제네릭 타입 매개변수이다. 이 타입은 나중에 특정 타입으로 지정된다.
  • data: T[]: API에서 응답받는 데이터는 항상 배열 형태이다. 이 배열의 요소는 T 타입이다. 즉, 다양한 타입의 데이터를 받을 수 있다.


아래와 같이 구체적인 타입 정의가 가능하다.

PostResponse: 이 타입은 PostResponseTPost로 설정하여, data 필드가 Post 객체의 배열이 되도록 한다.

// src/model/posts.ts

import { ApiResponse } from "./api";

export interface Post {
  postId: number;
  title: string;
  body: string;
  date: string;
  tags: string[];
  user: User;
}

export interface User {
  userId: number;
  username: string;
}

// 구체적인 타입 정의
export type PostResponse = ApiResponse<Post>; // Post 객체의 배열


이제 PostResponse를 API 서비스에서 사용하여 응답 타입을 정의할 수 있다.

// src/api/posts.ts

import axios from "axios";
import { Post, PostResponse } from "../model/posts";

const API_URL = "https://example.com/api/posts";

// API에서 게시물을 가져오는 함수
export const fetchPosts = async (): Promise<PostResponse> => {
  const response = await axios.get<PostResponse>(API_URL);
  return response.data;
};


마지막으로 이제 App.tsx에서 fetchPosts를 사용하여 데이터를 가져오면 된다.

// src/App.tsx

import React, { useEffect, useState } from "react";
import PostsPage from "./PostsPage";
import { Post } from "./model/posts";
import UserPage from "./UserPage";
import { fetchPosts } from "./api/posts"; // import

const App: React.FC = () => {
  const [myPosts, setMyPosts] = useState<Post[]>([]);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const loadPosts = async () => {
      try {
        const posts = await fetchPosts();
        setMyPosts(posts.data); // posts.data로 실제 게시물 배열 접근
      } catch (err) {
        setError("게시물을 로드할 수 없습니다.");
      }
    };

    loadPosts();
  }, []);

  const changeTitle = (id: number, newTitle: string) => {
    const updatedPosts = myPosts.map((post) =>
      post.postId === id ? { ...post, title: newTitle } : post
    );
    setMyPosts(updatedPosts);
  };

  const showUserName = (userName: string) => {
    return userName;
  };

  if (error) {
    return <div>{error}</div>; // 에러 메시지 표시
  }

  return (
    <div>
      <PostsPage postInfo={myPosts} />
      <UserPage
        username="시은"
        email="sieun@example.com"
        isActive={true}
        showUserName={showUserName}
      />
    </div>
  );
};

export default App;



카테고리:

업데이트:

댓글남기기