Use Cases

Claude Code Vue.js dengan Claude Code

Pelajari tentang claude code vue.js menggunakan Claude Code. Dilengkapi tips praktis dan contoh kode.

Vue.jspengembanganでClaude Codeが活躍する場面

Vue.js Composition APIやPiniastore dll.、モダンな設計pola 豊富.Claude Code これら pola 理解 上 、type safety maintainability 高いVuekomponen efisien generate bisa dilakukan.

generate Composition APIkomponen

> Vue 3 Composition API dengan データテーブルkomponen buatkan.
> sort、filter、pagination機能 implementasikan.
> TypeScript dengan type safety.
<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="pencarian..."
      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>

generate Piniastore

> Pinia dengan ショッピングカートstore buatkan.
> 商品penambahan、数量変更、合計金額計算 implementasikan.
// 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,
});

pembuatan composable

> APIフェッチ用 composable buatkan.
> ローディング、error、リトライ機能付き dengan 。
// 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 };
}

serverAPIルート Nuxt.js

// 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),
  };
});

Testing

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);
  });
});

Summary

Dengan Claude Code, Vue.js / Nuxt.js komponen、store、composable、APIルート type safetyかつefisien pengembangan bisa dilakukan.Vue特有 Composition APIpolaやPinia 設計 juga Claude Code 熟知 い.プロンプト 書き方次第 品質 変わる untuk 、プロンプトテクニック完全panduan 参考 .proyek Vue規約 CLAUDE.md 記述 dan よい.

Untuk Claude Codeの詳細はAnthropic公式dokumenをご覧ください。Vue.jsの公式panduan, lihat Vue.js公式サイト.

#Claude Code #Vue.js #Nuxt.js #Pinia #Composition API