TypeScript 와 React
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 이라는 함수를 추가하게 되면?
typescriptsum('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의 경우
typescriptfunction 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가 없어도 에러가 발생되지 않는 것을 알 수 있다.
typescriptinterface 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
라는 값이 빠졌다는 에러 메시지를 출력하게 된다
typescriptTypeScript Error: Property 'age' is missing
클래스에서 접근 제어자 사용
TypeScript는 public
, private
, protected
키워드를 제공하여 클래스 멤버의 접근 범위를 제한할 수 있다.
javascriptclass Person {
constructor(name) {
this.name = name;
}
}
const person = new Person("Alice");
console.log(person.name);
JavaScript의 경우는 모든 속성이 공개되어 있어 외부에서 직접 접근이 가능하다
typescriptclass 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의 경우 명확한 타입 보장이 어렵다
typescriptfunction 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="리액트 강의" />