Vue.js と Django を基盤とした Web アプリ開発で遭遇する主要な技術的課題と対応策

Vue リアクティブデータ処理と初期化タイミング

クライアントサイドアプリケーションを実装する際、コンポーネントのマウント直後にデータを取得し、その結果に基づいて状態を管理する必要があります。この場合、ref で定義された変数の値が即座に同期されていないため、外部からの参照や次の画面への引数受け渡しにおいて予期しない初期値が保持される現象が発生します。

このようなケースでは、データの更新を監視する watch スコープを活用して、依存関係の追跡を明示的に行うことが推奨されます。以下は、セットアップ関数内で非同期処理を実行し、特定の状態が 0 となった場合にダイアログ表示条件を設定する例です。

import { ref, onMounted, watch } from 'vue';
import { store } from '@/store/index';

const itemCount = ref(0);
const dialogVisible = ref(false);
const purchaseQty = ref(1);
const cartProducts = ref([]);
const currentDetailStr = sessionStorage.getItem('currentItemDetail');
const detailData = JSON.parse(currentDetailStr || '{}');

// 初期ロード処理
onMounted(async () => {
  await fetchInitialData();

  // データ取得完了後の判定処理
  if (itemCount.value === 0) {
    dialogVisible.value = true;
  }

  productImageUrl.value = `${baseUrl}${detailData.image}`;
  productName.value = detailData.description;

  // セッションストレージから購入数を取得
  const savedQty = sessionStorage.getItem('buyCount');
  purchaseQty.value = savedQty ? Number(savedQty) : 1;

  cartProducts.value = [
    {
      name: detailData.name,
      description: detailData.description,
      image: detailData.image,
      quantity: purchaseQty.value
    }
  ];
});

// 数量変更時の連動処理
watch(purchaseQty, (newVal) => {
  const updatedProduct = {
    ...detailData,
    quantity: newVal
  };
  cartProducts.value = [updatedProduct];
});

// 外部ロジックからの直接代入例
setInitialQuantity(() => {
  const qty = Number(sessionStorage.getItem('buyCount')) || 1;
  purchaseQty.value = qty;
});

ルーティング設定とパス解決の問題点

ハッシュモードで動作する履歴マネージャー(createWebHashHistory)を使用する場合、サブルート設定時に誤ったスラッシュ記述が起きると遷移エラーが発生します。絶対パスを指定する際、末尾の / または最初の / に関する構文ルールを厳守する必要があります。

Element Plus コンポーネントの制限事項

表コンポーネント(el-table)を使用する際、バージョンアップによりデフォルト幅の設定が削除されました。これにより、動的サイズ計算が失敗して「ResizeObserver loop completed with undelivered notifications」エラーが発生します。これを回避するには、テーブルまたは列に対して明示的な width プロパティを付与してください。

また、リスト内の特定の行データを取得する際は、テンプレート内部で v-slot スコープ変数を正しく使用することが必須です。以下に、商品説明を表示するスロットの実装例を示します。

<template>
  <el-table :data="productList">
    <template v-slot="{ row }">
      <div class="product-desc">{{ row.description }}</div>
    </template>
  </el-table>
</template>

入力が含まれるフィールド(例:数量入力欄)を改修する場合は、イベントリスナを通じて親コンポーネントへの変更を伝播させます。

非同期レンダリングと DOM 制御

ストアやセッションストレージから取得したデータ配列を v-for でレンダリングする際、データがまだ初期状態で空の配列の場合はエラーになる可能性があります。必ず配列の長さチェックを行うために v-if を用いるか、key プロパティを適切に設定してリアクティブ性を確保します。

<div v-if="cartProducts.length > 0" class="wrapper">
  <div 
    v-for="(product, index) in cartProducts" 
    :key="index"
    class="detail-item"
  >
    <el-image :src="cdnUrl + product.image" style="width: 100px;" />
    <span>{{ product.description }}</span>
  </div>
</div>

Django REST framework のカスタムページネーション

サーバーサイドの API クラスを実装する際に、標準的なページネーション機能がリクエストコンテキストを必要とする場合があります。例えば、APIView からページネーションを適用する際、偽の HTTP リクエストオブジェクトを作成して渡す必要があることがあります。

以下の Python コードは、ユーザー名とステータスに基づいてオーダーを検索し、カスタムページネーターで分割するロジックを示しています。

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from django.core.exceptions import ValidationError

class OrderListPagination(PageNumberPagination):
    page_size = 5
    page_size_query_param = 'page_size'
    max_page_size = 100

class OrderSearchView(APIView):
    def post(self, request):
        data = request.data
        user_id = data.get("username")
        status = data.get("state")
        p_idx = int(data.get("page", 1))
        p_size = int(data.get("page_size", 5))

        queryset = OrderCart.objects.none()

        if status == 'pending':
            queryset = OrderCart.objects.filter(username=user_id, status='pending')
        elif status == 'completed':
            queryset = OrderCart.objects.filter(username=user_id, status='completed')
        elif status == 'shipped':
            queryset = OrderCart.objects.filter(username=user_id, status='shipped')
        else:
            queryset = OrderCart.objects.filter(username=user_id)

        # ページネーション用の仮のリクエスト生成
        # DRF のページネーターが必要とするコンテキスト情報を注入
        from rest_framework.request import Request
        from rest_framework.test import APIRequestFactory
        factory = APIRequestFactory()
        mock_req = factory.get('/', {'page': p_idx, 'page_size': p_size})
        fake_request = Request(mock_req)

        paginator = OrderListPagination()
        paginated_data = paginator.paginate_queryset(queryset, fake_request)
        
        serializer = OrderSerializer(paginated_data, many=True)
        return paginator.get_paginated_response(serializer.data)

データベース操作の最適化

アドレス情報などの関連レコードを作成する際、重複挿入を防ぐために get_or_create メソッドの利用が有効です。これにより、同一注文 ID に対する追加作成を防止できます。

# アドレス情報の一意な挿入処理
OrderAddress.objects.get_or_create(
    order_id=generated_order_number,
    defaults={
        'recipient_name': address_data.get('name'),
        'address_line': address_data.get('detail'),
        'phone': address_data.get('phone'),
        'postal_code': address_data.get('postal_code', '000000')
    }
)

同様に、後方互換性のために辞書型データを受け取る際にも、型チェックを行いながら安全にフィールドマッピングを行ってください。

タグ: vue.js Django REST Framework Element Plus javascript Python

6月18日 23:22 投稿