background
TypeScript3분

TypeScript 와 React

2025년 5월 8일

JavaScript는 프론트엔드 생태계 90% 이상, 100%를 육박하게 차지하고 있는 언어라고 봐도 무방하다. TypeScript는 이런 JavaScript의 단점을 보완하고, 여러 좋은 기능들을 추가한 언어라고 볼 수 있다.

TypeScript란?

TypeScript는 JavaScript의 모든 특징을 제공하면서, 그 위에 타입 시스템이라는 층을 추가한다.

이 말은 JavaScript의 단점을 보완하기 위해 만들어진 정적 타입 추가라는 의미로, JavaScript + Type = TypeScript라고 볼 수 있다.

TypeScript 사용 이유

에러의 사전 방지

javascript
// JS
function sum(a,b) {
	conslole.log(a + b);
}
typescript
// TS
function sum(a : number, b : number) {
	console.log(a + b);
}

이 두 코드에서 동일한 기능을 하는 sum 이라는 함수를 추가하게 되면?

typescript
sum('10', '20');
typescript
// JS
'1020'
// TS
Error : '10'number에 할당될 수 없습니다.

다음과 같이 TS의 경우 에러가 발생하게 된다.

이 이유는 TypeScript의 경우 JavaScript 에서 컴파일 과정을 거치고 브라우저에서 실행되는데, 타입이 맞지 않다면, 컴파일 과정에서 잘못된 타입 할당을 방지한다.

함수에서 매개변수와 반환값의 타입 지정

JavaScript의 경우 함수의 매개변수 타입을 제한할 수 없다. 방금의 경우와 비슷하게 매개 변수의 값을 더해주는 이라는 값을 계산하는 함수가 있다고 가정 할 때

javascript
// JS
function add(a, b) {
	retrun a + b;
}

console.log(add(5, "10"));

이 함수를 실행하게 되면

javascript
"510" 이라는 결과가 발생하게 된다.

하지만 TypeScript의 경우

typescript
function add(a: number, b: number) : number {
	return a + b;
}

console.log(add(5, "10"));
typescript
// TypeScript Error: Argument of type 'string' is not assignable to parameter of type 'number'.

다음과 같은 오류가 발생하게 된다.

인터페이스 사용

TypeScript는 인터페이스를 활용하여 객체의 구조를 정의할 수 있다.

javascript
// JS
const user = {
	name: "Alice",
	age: 25
};

function greet(user) {
	console.log(`Hello, ${user.name}!`);
}

greet({name:"Bob"});

위와 같이 JavaScript의 경우 객체의 속성인 age가 없어도 에러가 발생되지 않는 것을 알 수 있다.

typescript
interface User {
	name: string;
	age: number;
}

const user : User = {
	name : "Alice",
	age: 25
};

function greet(user:User) {
	console.log(`Hello, ${user.name}!`);
}

greet({ name : "Bob" });

하지만 TypeScript의 경우 greet라는 함수에서 user라는 값을 받기를 기대했지만, 실제로 들어간 값은 name 하나기에 age 라는 값이 빠졌다는 에러 메시지를 출력하게 된다

typescript
TypeScript Error: Property 'age' is missing

클래스에서 접근 제어자 사용

TypeScript는 public , private , protected 키워드를 제공하여 클래스 멤버의 접근 범위를 제한할 수 있다.

javascript
class Person {
	constructor(name) {
		this.name = name;
	}
}

const person = new Person("Alice");
console.log(person.name);

JavaScript의 경우는 모든 속성이 공개되어 있어 외부에서 직접 접근이 가능하다

typescript
class Person {
	private name: string,
	
	constructor(name: string) {
		this.name = name;
	}
	
	getName(): string {
		return this.name;
	}
}

const person = new Person("Alice");
console.log(person.name); // TypeScript Error: Property 'name' is private
console.log(person.getName()); // Alice

TypeScript의 경우 private 를 통해 클래스 내부에서만 접근하도록 제한할 수 있다

제네릭 사용

TypeScript는 제네릭을 지원하여 재사용 가능한 코드를 작성할 수 있다.

typescript
// JS
function identity(value) {
	return value;
}

console.log(identity(10)); // 10
console.log(identity("Hello")); // "Hello"

JavaScript의 경우 명확한 타입 보장이 어렵다

typescript
function identity<T>(value: T) : T {
	return value;
}

console.log(identity<number>(10)); // 10
console.log(identity<string>("Hello")); // "Hello"

TypeScript의 제네릭을 사용하면 타입을 유지하면서도 범용적인 함수를 만들 수 있다

결론

  • TypeScript는 JavaScript의 단점을 보완하여 더욱 안전하고 유지보수하기 쉬운 코드를 작성할 수 있도록 도와줌
  • 버그를 줄이고, 코드의 가독성과 안정성을 높이는 데 큰 도움을 줌
  • 러닝 커브가 높지만, 익히게 되면 개발 속도와 협업 효율이 증가하게 됨

심화

YoutubeAPI 활용해서 동영상 보여주는 컴포넌트 생성하는 코드 비교

javascript
// JS
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const YoutubeVideoList = ({ apiKey, maxResults = 10, query = "리액트, 자바스크립트"}) => {
	const [videos, setVideos] = useState([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);
	const [nextPageToken, setNextPageToken] = useState(undefined);
	
	useEffect(() => {
		const fetchVideos = async () => {
			try {
				setLoading(true);
				const res = await axios.get(
					'https://www.googleapis.com/youtube/v3/search',
					{
						params : {
							part: 'snippet',
							maxResults,
							q: query,
							type: 'video',
							key: apiKey,
							pageToken : nextPageToken
						},
					}
				);
				
				setVideos(res.data.items);
				setNextPageToken(res.data.nextPageToken);
				setLoading(false);
			} catch (err) {
				setError('YouTube API 데이터를 가져오는 중 오류 발생!!');
				setLoading(false);
				console.error('API 오류:', err);
			}
		};
		
		fetchVideos();
	}, [apiKey, maxResults, query, nextPageToken]);
	
	const loadMoreVides = () => {
		if (nextPageToken) {
			setNextPageToken(nextPageToken);
		}
	};
	
	if (loading) return <div>로딩 중...</div>;
	if (error) return <div>{error}</div>;
	
	 return (
    <div className="youtube-video-list">
      <h2>{query} 검색 결과</h2>
      <div className="video-grid">
        {videos.map((video) => (
          <div key={video.id.videoId} className="video-item">
            <a
              href={`https://www.youtube.com/watch?v=${video.id.videoId}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              <img
                src={video.snippet.thumbnails.medium.url}
                alt={video.snippet.title}
                className="video-thumbnail"
              />
              <h3 className="video-title">{video.snippet.title}</h3>
            </a>
            <p className="channel-name">{video.snippet.channelTitle}</p>
            <p className="video-description">{video.snippet.description.substring(0, 100)}...</p>
            <p className="publish-date">
              {new Date(video.snippet.publishedAt).toLocaleDateString()}
            </p>
          </div>
        ))}
      </div>
      {nextPageToken && (
        <button onClick={loadMoreVideos} className="load-more-button">
          더 보기
        </button>
      )}
    </div>
  );
}

exprot default YouTubeVideoList;
typescript
// TS
import React, { useState, useEffect } from 'react';
import axios from 'axios';

// 타입 정의
interface Video {
  id: {
    videoId: string;
  };
  snippet: {
    title: string;
    description: string;
    thumbnails: {
      medium: {
        url: string;
      };
    };
    channelTitle: string;
    publishedAt: string;
  };
}

interface YouTubeApiResponse {
  items: Video[];
  nextPageToken?: string;
  prevPageToken?: string;
}

interface VideoListProps {
  apiKey: string;
  maxResults?: number;
  query?: string;
}

const YouTubeVideoList: React.FC<VideoListProps> = ({ 
  apiKey, 
  maxResults = 10, 
  query = 'react tutorials' 
}) => {
  const [videos, setVideos] = useState<Video[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [nextPageToken, setNextPageToken] = useState<string | undefined>(undefined);

  useEffect(() => {
    const fetchVideos = async () => {
      try {
        setLoading(true);
        const response = await axios.get<YouTubeApiResponse>(
          'https://www.googleapis.com/youtube/v3/search',
          {
            params: {
              part: 'snippet',
              maxResults,
              q: query,
              type: 'video',
              key: apiKey,
              pageToken: nextPageToken,
            },
          }
        );

        setVideos(response.data.items);
        setNextPageToken(response.data.nextPageToken);
        setLoading(false);
      } catch (err) {
        setError('YouTube API 데이터를 가져오는 중 오류가 발생했습니다.');
        setLoading(false);
        console.error('API 오류:', err);
      }
    };

    fetchVideos();
  }, [apiKey, maxResults, query, nextPageToken]);

  const loadMoreVideos = (): void => {
    // 다음 페이지 토큰이 있을 경우에만 실행
    if (nextPageToken) {
      setNextPageToken(nextPageToken);
    }
  };

  if (loading) return <div>로딩 중...</div>;
  if (error) return <div>{error}</div>;

  return (
    <div className="youtube-video-list">
      <h2>{query} 검색 결과</h2>
      <div className="video-grid">
        {videos.map((video) => (
          <div key={video.id.videoId} className="video-item">
            <a
              href={`https://www.youtube.com/watch?v=${video.id.videoId}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              <img
                src={video.snippet.thumbnails.medium.url}
                alt={video.snippet.title}
                className="video-thumbnail"
              />
              <h3 className="video-title">{video.snippet.title}</h3>
            </a>
            <p className="channel-name">{video.snippet.channelTitle}</p>
            <p className="video-description">{video.snippet.description.substring(0, 100)}...</p>
            <p className="publish-date">
              {new Date(video.snippet.publishedAt).toLocaleDateString()}
            </p>
          </div>
        ))}
      </div>
      {nextPageToken && (
        <button onClick={loadMoreVideos} className="load-more-button">
          더 보기
        </button>
      )}
    </div>
  );
};

export default YouTubeVideoList;

// 사용 예시
// <YouTubeVideoList apiKey="YOUR_API_KEY" maxResults={12} query="리액트 강의" />

태그

#React#TypeScript