Use Cases

Claude Code Vue.js with Claude Code

Learn about claude code vue.js using Claude Code. Practical tips and code examples included.

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

Summary

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