10 분 소요


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가지 방법이 있다. (큰 차이 없음)
// 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을 지정해준다.

// 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을 지정해야한다.

방법 1) 함수를 작성하고 event 넣은 후 마우스 올려보기

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

어떤 type을 썼는지 타입 추론이 가능하다.


type 복사 붙여넣기!

// 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>

          <input
            type="text"
            value={newTitle}
            // event 타입 정의
            onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
              setNewTitle(e.target.value)
            }
            placeholder="새로운 제목 입력"
          />
          <button onClick={() => changeTitle(post.postId, newTitle)}>
            제목 변경
          </button>
        </li>
      ))}
    </ul>
  );
};

export default PostsPage;



방법 2) 메서드에다 호버링할 때 나오는 ...EventHandler...를 통해 type을 추론할 수 있다.

onClick일 경우

import React, { MouseEvent, useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e: MouseEvent<HTMLDivElement>) => {
    e.target;
  };

  return <div onClick={increment}>{counter}</div>;
};

export default App;


onChange일 경우

import React, { FormEvent, useState } from "react";

const App = () => {
  const [counter, setCounter] = useState<number>(1);
  const increment = (e: FormEvent<HTMLDivElement>) => {
    e.target;
  };

  return <div onChange={increment}>{counter}</div>;
};

export default App;



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를 제외한 새로운 타입을 생성한다.

아래는 User 인터페이스에서 userId를 제외하고 새로운 속성을 추가한 UserProps 타입을 정의하는 예시이다.

// src/UserPage.tsx

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

// User에서 userId를 제외하고 새로운 속성을 추가한 타입 정의
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;


또한, type을 사용하여 Omit을 적용하는 방법은 아래와 같다.

// src/model/posts.ts

export type PostWithoutTags = Omit<Post, "tags">;


7.2 Pick

Pick<T, K>는 타입 T에서 속성 K만 선택하여 새로운 타입을 만든다.

아래는 Post 타입에서 postId만 선택한 새로운 타입을 정의한 예시이다.

// src/PostsPage.tsx

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

// Pick을 사용해 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;



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;



1. 비동기 함수에서 TS 사용하기

1.1 json 서버 열기

먼저 json 서버를 설치해주자

yarn add json-server


그 후, db.json 파일을 만들어 아래와 같은 내용을 넣어주자

{
  "people": [
    { "id": "1", "age": "25", "height": 179 },
    { "id": "2", "age": "22", "height": 163 },
    { "id": "3", "age": "29", "height": 184 }
  ]
}


아래 명령을 통해 json-sever를 실행하자

yarn json-server --watch db.json --port 3001


1.2 비동기 함수와 TS

아래 비동기 함수를 보자

async function getPost() {
  const res = await fetch(`http://localhost:3001/people`);
  if (!res.ok) {
    throw new Error();
  }
  return res.json();
}
getPost().then((res) => console.log(res));

아래 명령어로 실행하기!

ts-node 파일명.ts

res보면 any 타입인 것을 확인할 수 있다. any 타입은 모든 타입을 허용하기 때문에 (=치트키)TypeScript가 제공하는 강력한 정적 타입 검사 기능을 이용할 수 없게 된다.


any 타입을 피하고 타입을 명시적으로 정의하여 타입 안정성을 보장하도록 해보자.

함수를 선언하는 부분에 Promise<Person[]> 작성하기

type Person = { id: number; age: number; height: number };

// getperson이 반환하는 건 person의 array
// 비동기 함수는 promise를 반환하기 때문에 promise 타입을 사용해야함
async function getperson(): Promise<Person[]> {
  const res = await fetch("http://localhost:3001/people");
  if (!res.ok) {
    throw new Error();
  }
  return res.json();
}

getperson().then((res) => console.log(res[0].age));


이 외에 return 부분에 type을 작성해줘도 함수의 type이 return부의 type으로 자동으로 추론되기 때문에 아래와 같이 작성해도 된다! (근데 Promise가 더 간단한 듯..)

type Person = { id: number; age: number; height: number };

async function getperson() {
  const res = await fetch("http://localhost:3001/people");
  if (!res.ok) {
    throw new Error();
  }
  return res.json() as any as Person[];
}

getperson().then((res) => console.log(res[0].age));



참조

  • [코딩에 진심인 사람을 위해 준비한 리액트 타입스크립트 실제 회사에서 쓰는 레벨 ver](https://www.youtube.com/watch?v=V9XLst8UEtk&t=102s)


카테고리:

업데이트:

댓글남기기