Claude Code Vue.js: Claude Code 활용 가이드
claude code vue.js: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.
Vue.js개발でClaude Code가 活躍する場面
Vue.jsはComposition APIやPiniaストアなど、モダンな설계パターンが豊富です。Claude Code는 이것らのパターンを理解した上で、타입安全で유지보수성の高いVue컴포넌트を효율적으로생성할 수 있습니다。
Composition API컴포넌트の생성
> Vue 3のComposition APIで데이터테이블컴포넌트를 생성해줘。
> 정렬、필터、페이지네이션機能를 구현해줘。
> TypeScriptで타입安全にして。
<script setup lang="ts">
import { ref, computed } from 'vue';
interface Column<T> {
key: keyof T;
label: string;
sortable?: boolean;
}
interface Props<T> {
data: T[];
columns: Column<T>[];
pageSize?: number;
}
const props = withDefaults(defineProps<Props<Record<string, unknown>>>(), {
pageSize: 10,
});
const sortKey = ref<string>('');
const sortOrder = ref<'asc' | 'desc'>('asc');
const filterText = ref('');
const currentPage = ref(1);
const filteredData = computed(() => {
if (!filterText.value) return props.data;
const query = filterText.value.toLowerCase();
return props.data.filter(row =>
Object.values(row).some(val =>
String(val).toLowerCase().includes(query)
)
);
});
const sortedData = computed(() => {
if (!sortKey.value) return filteredData.value;
return [...filteredData.value].sort((a, b) => {
const aVal = a[sortKey.value];
const bVal = b[sortKey.value];
const cmp = String(aVal).localeCompare(String(bVal));
return sortOrder.value === 'asc' ? cmp : -cmp;
});
});
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * props.pageSize;
return sortedData.value.slice(start, start + props.pageSize);
});
const totalPages = computed(() =>
Math.ceil(filteredData.value.length / props.pageSize)
);
function handleSort(key: string) {
if (sortKey.value === key) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
} else {
sortKey.value = key;
sortOrder.value = 'asc';
}
}
</script>
<template>
<div>
<input
v-model="filterText"
type="text"
placeholder="検索..."
class="mb-4 w-full rounded border px-3 py-2"
@input="currentPage = 1"
/>
<table class="w-full border-collapse">
<thead>
<tr>
<th
v-for="col in columns"
:key="String(col.key)"
class="cursor-pointer border-b px-4 py-2 text-left"
@click="col.sortable && handleSort(String(col.key))"
>
{{ col.label }}
<span v-if="sortKey === col.key">
{{ sortOrder === 'asc' ? ' ↑' : ' ↓' }}
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in paginatedData" :key="i" class="hover:bg-gray-50">
<td v-for="col in columns" :key="String(col.key)" class="border-b px-4 py-2">
{{ row[col.key] }}
</td>
</tr>
</tbody>
</table>
<div class="mt-4 flex items-center justify-between">
<span class="text-sm text-gray-600">
{{ filteredData.length }}件中 {{ (currentPage - 1) * pageSize + 1 }}〜{{ Math.min(currentPage * pageSize, filteredData.length) }}件
</span>
<div class="flex gap-2">
<button
:disabled="currentPage <= 1"
class="rounded border px-3 py-1 disabled:opacity-50"
@click="currentPage--"
>
Previous
</button>
<button
:disabled="currentPage >= totalPages"
class="rounded border px-3 py-1 disabled:opacity-50"
@click="currentPage++"
>
Next
</button>
</div>
</div>
</div>
</template>
Piniaストアの생성
> Piniaでショッピングカートストア를 생성해줘。
> 상품추가、数量변경、合計金額計算를 구현해줘。
// stores/cart.ts
import { defineStore } from 'pinia';
interface CartItem {
productId: string;
name: string;
price: number;
quantity: number;
image: string;
}
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
}),
getters: {
totalItems: (state) =>
state.items.reduce((sum, item) => sum + item.quantity, 0),
totalPrice: (state) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
formattedTotal(): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(this.totalPrice);
},
},
actions: {
addItem(product: Omit<CartItem, 'quantity'>) {
const existing = this.items.find(i => i.productId === product.productId);
if (existing) {
existing.quantity++;
} else {
this.items.push({ ...product, quantity: 1 });
}
},
updateQuantity(productId: string, quantity: number) {
const item = this.items.find(i => i.productId === productId);
if (!item) return;
if (quantity <= 0) {
this.removeItem(productId);
} else {
item.quantity = quantity;
}
},
removeItem(productId: string) {
this.items = this.items.filter(i => i.productId !== productId);
},
clearCart() {
this.items = [];
},
},
persist: true,
});
コンポーザブルの생성
> APIフェッチ用のコンポーザブル를 생성해줘。
> ローディング、에러、リトライ機能付きで。
// composables/useFetch.ts
import { ref, watchEffect, type Ref } from 'vue';
interface UseFetchOptions<T> {
immediate?: boolean;
defaultValue?: T;
transform?: (data: unknown) => T;
}
export function useFetch<T>(
url: string | Ref<string>,
options: UseFetchOptions<T> = {}
) {
const data = ref<T | null>(options.defaultValue ?? null) as Ref<T | null>;
const error = ref<Error | null>(null);
const loading = ref(false);
async function execute() {
loading.value = true;
error.value = null;
try {
const resolvedUrl = typeof url === 'string' ? url : url.value;
const res = await fetch(resolvedUrl);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
data.value = options.transform ? options.transform(json) : json;
} catch (err) {
error.value = err as Error;
} finally {
loading.value = false;
}
}
async function retry() {
await execute();
}
if (options.immediate !== false) {
if (typeof url !== 'string') {
watchEffect(() => execute());
} else {
execute();
}
}
return { data, error, loading, execute, retry };
}
Nuxt.jsでの서버APIルート
// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const page = Number(query.page) || 1;
const limit = Number(query.limit) || 20;
const products = await prisma.product.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
});
const total = await prisma.product.count();
return {
items: products,
total,
page,
totalPages: Math.ceil(total / limit),
};
});
테스트
import { describe, it, expect } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { useCartStore } from '@/stores/cart';
describe('Cart Store', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it('should add item to cart', () => {
const cart = useCartStore();
cart.addItem({ productId: '1', name: 'Test', price: 1000, image: '/test.jpg' });
expect(cart.items).toHaveLength(1);
expect(cart.totalItems).toBe(1);
});
it('should increment quantity for existing item', () => {
const cart = useCartStore();
cart.addItem({ productId: '1', name: 'Test', price: 1000, image: '/test.jpg' });
cart.addItem({ productId: '1', name: 'Test', price: 1000, image: '/test.jpg' });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].quantity).toBe(2);
});
it('should calculate total price', () => {
const cart = useCartStore();
cart.addItem({ productId: '1', name: 'A', price: 1000, image: '' });
cart.addItem({ productId: '2', name: 'B', price: 2000, image: '' });
expect(cart.totalPrice).toBe(3000);
});
});
정리
Claude Code를 활용하면 Vue.js / Nuxt.jsの컴포넌트、ストア、コンポーザブル、APIルートを타입安全かつ효율적으로개발할 수 있습니다。Vue特有のComposition APIパターンやPiniaの설계もClaude Code는 熟知しています。プロンプトの書き方次第で品質が変わるため、プロンプトテクニック完全가이드를 참고하세요.프로젝트のVue規約はCLAUDE.mdに記述しておくとよいでしょう。
Claude Code의 상세 정보는Anthropic공식 문서를 확인하세요.Vue.jsの公式가이드はVue.js공식 사이트를 참고하세요.
무료 PDF: 5분 완성 Claude Code 치트시트
이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.
개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code 다국어 글을 매일 발행하기 전에 확인할 7가지
누락된 언어, 깨진 CTA, 반영되지 않은 배포를 막기 위해 다국어 Claude Code 글을 매일 발행하기 전에 확인할 체크리스트입니다.
Codex Automations란? 잠자는 동안 AI가 콘텐츠 운영을 처리하게 하는 방법
Codex Automations로 트래픽 분석, 주제 선정, 글 작성, CTA 개선, 배포까지 자동화하는 실전 가이드.
Claude Code × GCP Cloud Functions 완전 가이드 | 서버리스 함수 초고속 개발
Claude Code로 GCP Cloud Functions를 효율화. HTTP/Pub/Sub/Firestore 트리거 구현부터 로컬 테스트·배포 자동화까지, Masa의 실무 경험을 토대로 실제 코드로 해설.