학습 목표
- AI를 활용한 React 컴포넌트 자동 생성 마스터하기
- Vue.js Composition API와 AI 통합 활용하기
- Angular의 복잡한 구조를 AI로 간단하게 관리하기
- 상태 관리 라이브러리 자동 설정 및 최적화
- 프레임워크별 베스트 프랙티스 적용하기
React와 AI 기반 개발
Cursor AI는 React의 컴포넌트 구조를 이해하고, 재사용 가능한 컴포넌트를 자동으로 생성합니다. Hooks, Context API, 최신 패턴까지 모두 지원합니다.
React 컴포넌트 자동 생성
요구사항: 검색 가능한 사용자 목록 컴포넌트
AI에게 요청: "페이지네이션과 검색 기능이 있는 사용자 목록 컴포넌트를 만들어줘"
AI가 생성한 컴포넌트
// UserList.tsx
import React, { useState, useEffect, useMemo } from 'react';
import { User } from '../types/User';
import { useDebounce } from '../hooks/useDebounce';
import './UserList.css';
interface UserListProps {
users: User[];
itemsPerPage?: number;
onUserSelect?: (user: User) => void;
}
export const UserList: React.FC = ({
users,
itemsPerPage = 10,
onUserSelect
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [sortConfig, setSortConfig] = useState<{
key: keyof User;
direction: 'asc' | 'desc';
} | null>(null);
const debouncedSearchTerm = useDebounce(searchTerm, 300);
// 필터링된 사용자 목록
const filteredUsers = useMemo(() => {
return users.filter(user => {
const searchLower = debouncedSearchTerm.toLowerCase();
return (
user.name.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower) ||
user.role?.toLowerCase().includes(searchLower)
);
});
}, [users, debouncedSearchTerm]);
// 정렬된 사용자 목록
const sortedUsers = useMemo(() => {
if (!sortConfig) return filteredUsers;
return [...filteredUsers].sort((a, b) => {
const aValue = a[sortConfig.key];
const bValue = b[sortConfig.key];
if (aValue < bValue) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
}, [filteredUsers, sortConfig]);
// 페이지네이션
const totalPages = Math.ceil(sortedUsers.length / itemsPerPage);
const paginatedUsers = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
return sortedUsers.slice(startIndex, startIndex + itemsPerPage);
}, [sortedUsers, currentPage, itemsPerPage]);
// 검색어 변경 시 첫 페이지로
useEffect(() => {
setCurrentPage(1);
}, [debouncedSearchTerm]);
const handleSort = (key: keyof User) => {
let direction: 'asc' | 'desc' = 'asc';
if (sortConfig?.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
};
return (
setSearchTerm(e.target.value)}
className="search-input"
/>
{searchTerm && (
)}
{filteredUsers.length}명의 사용자
handleSort('name')}>
이름
{sortConfig?.key === 'name' && (
)}
handleSort('email')}>
이메일
{sortConfig?.key === 'email' && (
)}
handleSort('role')}>
역할
{sortConfig?.key === 'role' && (
)}
상태
작업
{paginatedUsers.length === 0 ? (
검색 결과가 없습니다
) : (
paginatedUsers.map(user => (
onUserSelect?.(user)}
className="user-row"
>
{user.name}
{user.email}
{user.role}
{user.isActive ? '활성' : '비활성'}
))
)}
{totalPages > 1 && (
{[...Array(totalPages)].map((_, index) => (
))}
)}
);
};
AI가 생성한 커스텀 Hook
// hooks/useDebounce.ts
import { useState, useEffect } from 'react';
export function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// hooks/usePagination.ts
export function usePagination(items: T[], itemsPerPage: number) {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = Math.ceil(items.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentItems = items.slice(startIndex, endIndex);
const goToPage = (page: number) => {
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
};
const nextPage = () => goToPage(currentPage + 1);
const prevPage = () => goToPage(currentPage - 1);
return {
currentPage,
totalPages,
currentItems,
goToPage,
nextPage,
prevPage,
hasNext: currentPage < totalPages,
hasPrev: currentPage > 1
};
}
Vue.js와 AI 통합
Vue 3 Composition API 활용
AI가 생성한 Vue 3 컴포넌트
// TaskManager.vue
작업 관리
{{ stats.total }}
전체
{{ stats.completed }}
완료
{{ stats.pending }}
대기
{{ task.title }}
{{ formatDate(task.createdAt) }}
AI가 생성한 Pinia Store
// stores/task.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { v4 as uuidv4 } from 'uuid';
export interface Task {
id: string;
title: string;
description?: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
tags: string[];
createdAt: Date;
updatedAt: Date;
dueDate?: Date;
}
export const useTaskStore = defineStore('task', () => {
// 상태
const tasks = ref([]);
const isLoading = ref(false);
const error = ref(null);
// 게터
const completedTasks = computed(() =>
tasks.value.filter(task => task.completed)
);
const pendingTasks = computed(() =>
tasks.value.filter(task => !task.completed)
);
const tasksByPriority = computed(() => {
const priority = { high: 3, medium: 2, low: 1 };
return [...tasks.value].sort((a, b) =>
priority[b.priority] - priority[a.priority]
);
});
const overdueTasks = computed(() => {
const now = new Date();
return tasks.value.filter(task =>
task.dueDate && new Date(task.dueDate) < now && !task.completed
);
});
// 액션
function addTask(title: string, options: Partial = {}) {
const newTask: Task = {
id: uuidv4(),
title,
completed: false,
priority: 'medium',
tags: [],
createdAt: new Date(),
updatedAt: new Date(),
...options
};
tasks.value.unshift(newTask);
saveToLocalStorage();
return newTask;
}
function updateTask(id: string, updates: Partial) {
const index = tasks.value.findIndex(task => task.id === id);
if (index !== -1) {
tasks.value[index] = {
...tasks.value[index],
...updates,
updatedAt: new Date()
};
saveToLocalStorage();
}
}
function deleteTask(id: string) {
tasks.value = tasks.value.filter(task => task.id !== id);
saveToLocalStorage();
}
function toggleTask(id: string) {
const task = tasks.value.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
task.updatedAt = new Date();
saveToLocalStorage();
}
}
function clearCompleted() {
tasks.value = tasks.value.filter(task => !task.completed);
saveToLocalStorage();
}
// 로컬 스토리지
function saveToLocalStorage() {
localStorage.setItem('tasks', JSON.stringify(tasks.value));
}
function loadFromLocalStorage() {
const saved = localStorage.getItem('tasks');
if (saved) {
tasks.value = JSON.parse(saved).map((task: any) => ({
...task,
createdAt: new Date(task.createdAt),
updatedAt: new Date(task.updatedAt),
dueDate: task.dueDate ? new Date(task.dueDate) : undefined
}));
}
}
// 초기화
loadFromLocalStorage();
return {
// 상태
tasks,
isLoading,
error,
// 게터
completedTasks,
pendingTasks,
tasksByPriority,
overdueTasks,
// 액션
addTask,
updateTask,
deleteTask,
toggleTask,
clearCompleted
};
});
Angular와 AI 기반 개발
Angular 컴포넌트와 서비스
AI가 생성한 Angular 컴포넌트
// product-list.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject, combineLatest } from 'rxjs';
import {
takeUntil,
debounceTime,
distinctUntilChanged,
startWith,
map
} from 'rxjs/operators';
import { ProductService } from '../../services/product.service';
import { Product } from '../../models/product.model';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit, OnDestroy {
products$ = this.productService.products$;
loading$ = this.productService.loading$;
error$ = this.productService.error$;
searchControl = new FormControl('');
categoryControl = new FormControl('all');
sortControl = new FormControl('name');
categories = ['all', 'electronics', 'clothing', 'books', 'food'];
sortOptions = [
{ value: 'name', label: '이름순' },
{ value: 'price-asc', label: '가격 낮은순' },
{ value: 'price-desc', label: '가격 높은순' },
{ value: 'rating', label: '평점순' }
];
filteredProducts$ = combineLatest([
this.products$,
this.searchControl.valueChanges.pipe(
startWith(''),
debounceTime(300),
distinctUntilChanged()
),
this.categoryControl.valueChanges.pipe(startWith('all')),
this.sortControl.valueChanges.pipe(startWith('name'))
]).pipe(
map(([products, search, category, sort]) =>
this.filterAndSortProducts(products, search, category, sort)
)
);
private destroy$ = new Subject();
constructor(private productService: ProductService) {}
ngOnInit(): void {
this.productService.loadProducts();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private filterAndSortProducts(
products: Product[],
search: string,
category: string,
sortBy: string
): Product[] {
let filtered = products;
// 검색 필터
if (search) {
const searchLower = search.toLowerCase();
filtered = filtered.filter(product =>
product.name.toLowerCase().includes(searchLower) ||
product.description?.toLowerCase().includes(searchLower)
);
}
// 카테고리 필터
if (category !== 'all') {
filtered = filtered.filter(product =>
product.category === category
);
}
// 정렬
return this.sortProducts(filtered, sortBy);
}
private sortProducts(products: Product[], sortBy: string): Product[] {
const sorted = [...products];
switch (sortBy) {
case 'name':
return sorted.sort((a, b) =>
a.name.localeCompare(b.name)
);
case 'price-asc':
return sorted.sort((a, b) => a.price - b.price);
case 'price-desc':
return sorted.sort((a, b) => b.price - a.price);
case 'rating':
return sorted.sort((a, b) => b.rating - a.rating);
default:
return sorted;
}
}
addToCart(product: Product): void {
this.productService.addToCart(product);
}
trackByProductId(index: number, product: Product): string {
return product.id;
}
}
Angular 템플릿
검색
search
카테고리
모든 카테고리
{{ category | titlecase }}
정렬
{{ option.label }}
{{ product.name }}
{{ product.description }}
search_off
검색 결과가 없습니다
상품을 불러오는 중...
Angular 서비스
// product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, tap, map, finalize } from 'rxjs/operators';
import { Product } from '../models/product.model';
import { NotificationService } from './notification.service';
@Injectable({
providedIn: 'root'
})
export class ProductService {
private productsSubject = new BehaviorSubject([]);
private loadingSubject = new BehaviorSubject(false);
private errorSubject = new BehaviorSubject(null);
products$ = this.productsSubject.asObservable();
loading$ = this.loadingSubject.asObservable();
error$ = this.errorSubject.asObservable();
private apiUrl = '/api/products';
constructor(
private http: HttpClient,
private notification: NotificationService
) {}
loadProducts(): void {
this.loadingSubject.next(true);
this.errorSubject.next(null);
this.http.get(this.apiUrl).pipe(
tap(products => this.productsSubject.next(products)),
catchError(error => {
const message = '상품을 불러오는데 실패했습니다';
this.errorSubject.next(message);
this.notification.error(message);
return throwError(error);
}),
finalize(() => this.loadingSubject.next(false))
).subscribe();
}
getProductById(id: string): Observable {
return this.products$.pipe(
map(products => products.find(p => p.id === id))
);
}
addToCart(product: Product): void {
// 장바구니 로직
this.notification.success(`${product.name}을(를) 장바구니에 담았습니다`);
}
updateProduct(id: string, updates: Partial): Observable {
return this.http.patch(`${this.apiUrl}/${id}`, updates).pipe(
tap(updatedProduct => {
const products = this.productsSubject.value;
const index = products.findIndex(p => p.id === id);
if (index !== -1) {
products[index] = updatedProduct;
this.productsSubject.next([...products]);
}
}),
catchError(error => {
this.notification.error('상품 업데이트에 실패했습니다');
return throwError(error);
})
);
}
}
상태 관리 라이브러리 통합
프레임워크별 상태 관리
React: Redux Toolkit
// store/userSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { userAPI } from '../api/userAPI';
interface UserState {
currentUser: User | null;
isAuthenticated: boolean;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
currentUser: null,
isAuthenticated: false,
loading: false,
error: null
};
export const login = createAsyncThunk(
'user/login',
async (credentials: LoginCredentials) => {
const response = await userAPI.login(credentials);
return response.data;
}
);
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
logout(state) {
state.currentUser = null;
state.isAuthenticated = false;
},
updateProfile(state, action: PayloadAction>) {
if (state.currentUser) {
state.currentUser = { ...state.currentUser, ...action.payload };
}
}
},
extraReducers: (builder) => {
builder
.addCase(login.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(login.fulfilled, (state, action) => {
state.loading = false;
state.currentUser = action.payload.user;
state.isAuthenticated = true;
})
.addCase(login.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || '로그인 실패';
});
}
});
export const { logout, updateProfile } = userSlice.actions;
export default userSlice.reducer;
Vue: Pinia
// stores/user.ts
import { defineStore } from 'pinia';
import { userAPI } from '@/api/userAPI';
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null as User | null,
isAuthenticated: false,
loading: false,
error: null as string | null
}),
getters: {
userFullName: (state) => {
if (!state.currentUser) return '';
return `${state.currentUser.firstName} ${state.currentUser.lastName}`;
},
userPermissions: (state) => {
return state.currentUser?.permissions || [];
}
},
actions: {
async login(credentials: LoginCredentials) {
this.loading = true;
this.error = null;
try {
const { data } = await userAPI.login(credentials);
this.currentUser = data.user;
this.isAuthenticated = true;
// 토큰 저장
localStorage.setItem('authToken', data.token);
return data;
} catch (error) {
this.error = error.message;
throw error;
} finally {
this.loading = false;
}
},
logout() {
this.currentUser = null;
this.isAuthenticated = false;
localStorage.removeItem('authToken');
},
updateProfile(updates: Partial) {
if (this.currentUser) {
this.currentUser = { ...this.currentUser, ...updates };
}
}
},
persist: {
enabled: true,
strategies: [{
key: 'user',
storage: localStorage,
paths: ['currentUser', 'isAuthenticated']
}]
}
});
Angular: NgRx
// store/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User, LoginCredentials } from '../models/user.model';
export const login = createAction(
'[Auth] Login',
props<{ credentials: LoginCredentials }>()
);
export const loginSuccess = createAction(
'[Auth] Login Success',
props<{ user: User; token: string }>()
);
export const loginFailure = createAction(
'[Auth] Login Failure',
props<{ error: string }>()
);
export const logout = createAction('[Auth] Logout');
// store/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';
export interface UserState {
currentUser: User | null;
isAuthenticated: boolean;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
currentUser: null,
isAuthenticated: false,
loading: false,
error: null
};
export const userReducer = createReducer(
initialState,
on(UserActions.login, state => ({
...state,
loading: true,
error: null
})),
on(UserActions.loginSuccess, (state, { user }) => ({
...state,
currentUser: user,
isAuthenticated: true,
loading: false
})),
on(UserActions.loginFailure, (state, { error }) => ({
...state,
error,
loading: false
})),
on(UserActions.logout, () => initialState)
);
실습: 프레임워크 통합 프로젝트
실시간 대시보드 만들기
AI와 함께 실시간 데이터 대시보드를 구축해봅시다.
요구사항
- 실시간 차트 (매출, 사용자, 트래픽)
- 데이터 필터링 (날짜, 카테고리)
- 반응형 그리드 레이아웃
- WebSocket 실시간 업데이트
- 다크모드 지원
구현 단계
1. 프로젝트 설정
선택한 프레임워크로 프로젝트 초기화
# React
npx create-react-app dashboard --template typescript
# Vue
npm create vue@latest dashboard
# Angular
ng new dashboard --routing --style=scss
2. AI와 컴포넌트 설계
Chat에 요청: "실시간 대시보드를 위한 컴포넌트 구조를 설계해줘"
3. 차트 컴포넌트 생성
Cmd+K: "Chart.js를 사용한 실시간 차트 컴포넌트 만들어줘"
4. WebSocket 연결
Composer로 실시간 데이터 스트림 구현
5. 반응형 레이아웃
AI에게: "CSS Grid로 반응형 대시보드 레이아웃 만들어줘"
🌟 프레임워크별 팁
- React: React.memo()와 useMemo()로 성능 최적화
- Vue: Composition API로 로직 재사용성 향상
- Angular: OnPush 전략으로 변경 감지 최적화
- 공통: 가상 스크롤링으로 대량 데이터 처리
핵심 정리
컴포넌트 자동 생성
AI가 프레임워크별 베스트 프랙티스를 적용한 컴포넌트를 자동으로 생성합니다.
상태 관리 통합
Redux, Pinia, NgRx 등 각 프레임워크에 최적화된 상태 관리 코드를 생성합니다.
타입 안전성 보장
TypeScript를 활용하여 컴파일 타임에 오류를 방지하는 안전한 코드를 작성합니다.
성능 최적화 자동화
각 프레임워크의 성능 최적화 기법을 자동으로 적용합니다.