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공식 사이트를 참고하세요.
#Claude Code
#Vue.js
#Nuxt.js
#Pinia
#Composition API
Related Posts
Use Cases
Use Cases
Claude Code로 리팩토링을 자동화하는 방법
Claude Code를 활용해 코드 리팩토링을 효율적으로 자동화하는 방법을 알아봅니다. 실전 프롬프트와 구체적인 리팩토링 패턴을 소개합니다.
Use Cases
Use Cases
Claude Code로 사이드 프로젝트 개발 속도를 극대화하는 방법 [예제 포함]
Claude Code를 활용해 개인 프로젝트 개발 속도를 획기적으로 높이는 방법을 알아봅니다. 실전 예제와 아이디어부터 배포까지의 워크플로를 포함합니다.
Use Cases
Use Cases
Complete CORS Configuration Guide: Claude Code 활용 가이드
complete cors configuration guide: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.