Vue3とVuexでショッピングカートを作成する方法

提供:Dev Guides
移動先:案内検索

著者は、 Open Source Initiative を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Vue.js は、パフォーマンスが高く、プログレッシブ Javascriptフレームワークです。 これはGitHubで人気のあるフレームワークであり、活発で役立つコミュニティがあります。

Vue Webフレームワークの機能を示すために、このチュートリアルでは、eコマースアプリのショッピングカートの作成について説明します。 このアプリは、商品情報を保存し、顧客が後でチェックアウトするために購入したい商品を保持します。 情報を保存するには、Vue.jsで広く使用されている状態管理ライブラリVuexを調べます。 これにより、ショッピングカートアプリケーションがデータをサーバーに永続化できるようになります。 また、Vuexを使用して非同期タスク管理を処理します。

チュートリアルを終了すると、次のような機能するショッピングカートアプリケーションが作成されます。

前提条件

ステップ1—VueCLIを使用したアプリケーションのセットアップ

バージョン4.5.0以降、 Vue CLI には、新しいプロジェクトを作成するときにVue3プリセットを選択するための組み込みオプションが用意されています。 最新バージョンのVueCLIを使用すると、Vue 3をそのまま使用して、既存のVue2プロジェクトをVue3に更新できます。 このステップでは、Vue CLIを使用してプロジェクトを作成し、フロントエンドの依存関係をインストールします。

まず、ターミナルから次のコマンドを実行して、最新バージョンのVueCLIをインストールします。

npm install -g @vue/cli  

これにより、VueCLIがシステムにグローバルにインストールされます。

注:一部のシステムでは、npmパッケージをグローバルにインストールすると、アクセス許可エラーが発生し、インストールが中断される可能性があります。 sudonpm installと一緒に使用しないことはセキュリティのベストプラクティスであるため、代わりにnpmのデフォルトディレクトリを変更することでこれを解決できます。 EACCESエラーが発生した場合は、公式のnpmドキュメントの指示に従ってください。


このコマンドで正しいバージョンを使用していることを確認してください。

vue --version

次のような出力が得られます。

Output@vue/cli 4.5.10

注:古いバージョンのVue CLIが既にグローバルにインストールされている場合は、ターミナルから次のコマンドを実行してアップグレードします。

npm update -g @vue/cli

これで、新しいプロジェクトを作成できます。

vue create vuex-shopping-cart

これは、VueCLIコマンドvue createを使用して、vuex-shopping-cartという名前のプロジェクトを作成します。 Vue CLIの詳細については、VueCLIを使用してVue.jsシングルページアプリを生成する方法をご覧ください。

次に、次のプロンプトが表示されます。

OutputVue CLI v4.5.10
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 2] babel, eslint)
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)
  Manually select features

このリストからManually select featuresオプションを選択します。

次に、Vueアプリをカスタマイズするための次のプロンプトが表示されます。

Output...
 ◉ Choose Vue version
 ◯ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
❯◯ Unit Testing
 ◯ E2E Testing

このリストから、Choose Vue versionRouter、およびVuexを選択します。 これにより、Vueのバージョンを選択し、VuexとVueルーターを使用できるようになります。

次に、Vueのバージョンとして3.x (Preview)を選択し、history modeにno(N)と答えて、構成をIn dedicated config fileにするオプションを選択します。 最後に、Nと答えて、将来のプロジェクトのためにセットアップを保存しないようにします。

この時点で、Vueがアプリケーションを作成します。

プロジェクトの作成後、次のコマンドを使用してフォルダーに移動します。

cd vuex-shopping-cart

まず、Flexboxに基づく無料のオープンソースCSSフレームワークであるBulmaをインストールします。 次のコマンドを実行して、プロジェクトにBulmaを追加します。

npm install bulma

プロジェクトでBulmaCSSを使用するには、アプリのエントリポイントであるmain.jsファイルを開きます。

nano src/main.js

次に、次の強調表示されたインポート行を追加します。

vuex-shopping-cart / src / main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './../node_modules/bulma/css/bulma.css'

createApp(App).use(store).use(router).mount('#app')

ファイルを保存して閉じます。

このアプリでは、Axiosモジュールを使用してサーバーにリクエストを送信します。 次のコマンドを実行して、Axiosモジュールを追加します。

npm install axios

次に、アプリを実行して、機能していることを確認します。

npm run serve

選択したブラウザでhttp://localhost:8080に移動します。 Vueアプリのウェルカムページがあります。

Vueが機能していることを確認したら、CTRL+Cでサーバーを停止します。

このステップでは、コンピューターにVue CLIをグローバルにインストールし、Vueプロジェクトを作成し、必要なnpmパッケージAxiosとBulmaをインストールし、main.jsファイルでプロジェクトにBulmaをインポートしました。 次に、アプリのデータを保存するためのバックエンドAPIを設定します。

ステップ2—バックエンドを設定する

このステップでは、Vueプロジェクトで動作する別のバックエンドを作成します。 これは、フロントエンドのVueアプリケーションとは別のプロジェクトフォルダーにあります。

まず、Vueディレクトリから移動します。

cd ..

cart-backendという名前の別のディレクトリを作成します。

mkdir cart-backend

バックエンドフォルダを作成したら、それを作業ディレクトリにします。

cd cart-backend

必要なファイルを使用してプロジェクトを初期化することから始めます。 次のコマンドを使用して、アプリのファイル構造を作成します。

touch server.js
touch server-cart-data.json
touch server-product-data.json

ここでtouchコマンドを使用して、空のファイルを作成します。 server.jsファイルはNode.jsサーバーを保持し、JSONはショップの製品とユーザーのショッピングカートのデータを保持します。

次に、次のコマンドを実行してpackage.jsonファイルを作成します。

npm init

npmとNodeの詳細については、Node.jsシリーズのコーディング方法をご覧ください。

これらのバックエンド依存関係をNodeプロジェクトにインストールします。

npm install concurrently express body-parser

Express は、Webアプリケーション用のノードフレームワークであり、APIリクエストを処理するための便利な抽象化を提供します。 同時には、ExpressバックエンドサーバーとVue.js開発サーバーを同時に実行するために使用されます。 最後に、body-parserは、APIへのリクエストを解析するExpressミドルウェアです。

次に、アプリケーションのルートにあるserver.jsファイルを開きます。

nano server.js

次に、次のコードを追加します。

cart-backend / server.js

const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');

const app = express();
const PRODUCT_DATA_FILE = path.join(__dirname, 'server-product-data.json');
const CART_DATA_FILE = path.join(__dirname, 'server-cart-data.json');

app.set('port', (process.env.PORT || 3000));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  next();
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});

このスニペットは、最初にノードモジュールをバックエンドに追加します。これには、ファイルシステムに書き込むためのfsモジュールと、ファイルパスの定義を容易にするためのpathモジュールが含まれます。 次に、Express appを初期化し、JSONファイルへの参照をPRODUCT_DATA_FILEおよびCART_DATA_FILEとして保存します。 これらはデータリポジトリとして使用されます。 最後に、Expressサーバーを作成し、ポートを設定し、応答ヘッダーを設定するミドルウェアを作成し、サーバーがポートでリッスンするように設定しました。 Expressの詳細については、Expressの公式ドキュメントを参照してください。

setHeaderメソッドは、HTTP応答のヘッダーを設定します。 この場合、Cache-Controlを使用してアプリのキャッシュを指示しています。 詳細については、Cache-Controlに関するMozillaDeveloperNetworkの記事をご覧ください。

次に、フロントエンドがショッピングカートにアイテムを追加するためにクエリを実行するAPIエンドポイントを作成します。 これを行うには、app.postを使用してHTTPPOST要求をリッスンします。

最後のapp.use()ミドルウェアの直後のserver.jsに次のコードを追加します。

cart-backend / server.js

...
app.use((req, res, next) => {
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.setHeader('Pragma', 'no-cache');
  res.setHeader('Expires', '0');
  next();
});

app.post('/cart', (req, res) => {
    fs.readFile(CART_DATA_FILE, (err, data) => {
      const cartProducts = JSON.parse(data);
      const newCartProduct = { 
        id: req.body.id,
        title: req.body.title,
        description: req.body.description,
        price: req.body.price,
        image_tag: req.body.image_tag, 
        quantity: 1 
      };
      let cartProductExists = false;
      cartProducts.map((cartProduct) => {
        if (cartProduct.id === newCartProduct.id) {
          cartProduct.quantity++;
          cartProductExists = true;
        }
      });
      if (!cartProductExists) cartProducts.push(newCartProduct);
      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
        res.setHeader('Cache-Control', 'no-cache');
        res.json(cartProducts);
      });
    });
  });

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`);
});

このコードは、フロントエンドからカートアイテムを含むリクエストオブジェクトを受け取り、プロジェクトのルートにあるserver-cart-data.jsonファイルに保存します。 こちらの製品は、JavaScriptオブジェクトidtitledescriptionpriceimage_tagquantityプロパティ。 このコードは、カートがすでに存在するかどうかもチェックして、繰り返し商品のリクエストがquantityのみを増加させることを確認します。

次に、ショッピングカートからアイテムを削除するためのAPIエンドポイントを作成するコードを追加します。 今回は、app.deleteを使用してHTTPDELETEリクエストをリッスンします。

前のエンドポイントの直後のserver.jsに次のコードを追加します。

cart-backend / server.js

...
      fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
        res.setHeader('Cache-Control', 'no-cache');
        res.json(cartProducts);
      });
    });
  });

app.delete('/cart/delete', (req, res) => {
  fs.readFile(CART_DATA_FILE, (err, data) => {
    let cartProducts = JSON.parse(data);
    cartProducts.map((cartProduct) => {
      if (cartProduct.id === req.body.id && cartProduct.quantity > 1) {
        cartProduct.quantity--;
      } else if (cartProduct.id === req.body.id && cartProduct.quantity === 1) {
        const cartIndexToRemove = cartProducts.findIndex(cartProduct => cartProduct.id === req.body.id);
        cartProducts.splice(cartIndexToRemove, 1);
      }
    });
    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(cartProducts);
    });
  });
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});

このコードは、カートから削除するアイテムを含むリクエストオブジェクトを受け取り、idを介してserver-cart-data.jsonファイルでこのアイテムをチェックします。 存在し、数量が1より大きい場合、カート内のアイテムの数量が差し引かれます。 それ以外の場合、アイテムの数量が1未満の場合、カートから削除され、残りのアイテムはserver-cart-data.jsonファイルに保存されます。

ユーザーに追加機能を提供するために、APIエンドポイントを作成して、ショッピングカートからすべてのアイテムを削除できるようになりました。 これは、DELETEリクエストもリッスンします。

前のエンドポイントの後に、次の強調表示されたコードをserver.jsに追加します。

cart-backend / server.js

...
    fs.writeFile(CART_DATA_FILE, JSON.stringify(cartProducts, null, 4), () => {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(cartProducts);
    });
  });
});

app.delete('/cart/delete/all', (req, res) => {
  fs.readFile(CART_DATA_FILE, () => {
    let emptyCart = [];
    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
      res.json(emptyCart);
    });
  });
});

app.listen(app.get('port'), () => {
  console.log(`Find the server at: http://localhost:${app.get('port')}/`); // eslint-disable-line no-console
});

このコードは、空の array を返すことにより、カートからすべてのアイテムを削除する役割を果たします。

次に、APIエンドポイントを作成して、製品ストレージからすべての製品を取得します。 これは、app.getを使用してGET要求をリッスンします。

前のエンドポイントの後のserver.jsに次のコードを追加します。

cart-backend / server.js

...
app.delete('/cart/delete/all', (req, res) => {
  fs.readFile(CART_DATA_FILE, () => {
    let emptyCart = [];
    fs.writeFile(CART_DATA_FILE, JSON.stringify(emptyCart, null, 4), () => {
      res.json(emptyCart);
    });
  });
});

app.get('/products', (req, res) => {
  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});
...

このコードは、ファイルシステムのネイティブreadFileメソッドを使用して、server-product-data.jsonファイル内のすべてのデータをフェッチし、JSON形式で返します。

最後に、カートストレージからすべてのアイテムを取得するためのAPIエンドポイントを作成します。

cart-backend / server.js

...
app.get('/products', (req, res) => {
  fs.readFile(PRODUCT_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

app.get('/cart', (req, res) => {
  fs.readFile(CART_DATA_FILE, (err, data) => {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});
...

同様に、このコードはファイルシステムのネイティブreadFileメソッドを使用して、server-cart-data.jsonファイル内のすべてのデータをフェッチし、JSON形式で返します。

server.jsファイルを保存して閉じます。

次に、テスト目的でJSONファイルにモックデータを追加します。

以前に作成したserver-cart-data.jsonファイルを開きます。

nano server-cart-data.json

次の製品オブジェクトの配列を追加します。

cart-backend / server-cart-data.json

[
    {
        "id": 2,
        "title": "MIKANO Engine",
        "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
        "price": 650.9,
        "image_tag": "diesel-engine.png",
        "quantity": 1
    },
    {
        "id": 3,
        "title": "SEFANG Engine",
        "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
        "price": 619.9,
        "image_tag": "sefang-engine.png",
        "quantity": 1
    }
]

これは、ユーザーのショッピングカートで起動する2つのエンジンを示しています。

ファイルを保存して閉じます。

次に、server-product-data.jsonファイルを開きます。

nano server-product-data.json

server-product-data.jsonファイルに次のデータを追加します。

cart-backend / server-product-data.json

[
    {
      "id": 1,
      "title": "CAT Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "CAT-engine.png",
      "created_at": 2020,
      "owner": "Colton",
      "owner_photo": "image-colton.jpg",
      "email": "[email protected]",
      "price": 719.9
    },
    {
      "id": 2,
      "title": "MIKANO Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis! ",
      "product_type": "power set/diesel engine",
      "image_tag": "diesel-engine.png",
      "created_at": 2020,
      "owner": "Colton",
      "owner_photo": "image-colton.jpg",
      "email": "[email protected]",
      "price": 650.9
    },
    {
      "id": 3,
      "title": "SEFANG Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "sefang-engine.png",
      "created_at": 2017,
      "owner": "Anne",
      "owner_photo": "image-anne.jpg",
      "email": "[email protected]",
      "price": 619.9
    },
    {
      "id": 4,
      "title": "CAT Engine",
      "description": "Lorem ipsum dolor sit amet, consectetur  dignissimos suscipit voluptatibus distinctio, error nostrum expedita omnis ipsum sit inventore aliquam sunt quam quis!",
      "product_type": "power set/diesel engine",
      "image_tag": "lawn-mower.png",
      "created_at": 2017,
      "owner": "Irene",
      "owner_photo": "image-irene.jpg",
      "email": "[email protected]",
      "price": 319.9
    }
    
  ]

これにより、ユーザーがカートに入れることができるすべての可能な製品が保持されます。

ファイルを保存して閉じます。

最後に、次のコマンドを実行してサーバーを実行します。

node server

あなたはあなたのターミナルでこのようなものを受け取るでしょう:

OutputFind the server at: http://localhost:3000/

このウィンドウでこのサーバーを実行したままにします。

最後に、Vueアプリでプロキシサーバーを設定します。 これにより、フロントエンドとバックエンド間の接続が可能になります。

Vueアプリのルートディレクトリに移動します。

cd ../vuex-shopping-cart

ターミナルで、次のコマンドを実行してVue構成ファイルを作成します。

nano vue.config.js

次に、次のコードを追加します。

vuex-shopping-cart / vue.config.js

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000/',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
}

これにより、フロントエンドからhttp://localhost:3000/のバックエンドサーバーにリクエストが送信されます。 プロキシ構成の詳細については、VuedevServer.proxyドキュメントを確認してください。

ファイルを保存して閉じます。

このステップでは、ショッピングカートのAPIエンドポイントを処理するサーバー側のコードを記述しました。 ファイル構造を作成することから始め、server.jsファイルに必要なコードを追加し、JSONファイルにデータを追加することで終了しました。 次に、フロントエンドの状態ストレージを設定します。

ステップ3—Vuexを使用した状態管理の設定

Vuexでは、ストアはアプリケーションの状態が保持される場所です。 アプリケーションの状態を更新するには、コンポーネント内でアクションをディスパッチして、ストアでミューテーションをトリガーする必要があります。 Vuexストアは、状態、ミューテーション、アクション、およびゲッターで構成されています。

このステップでは、これらの各部分を作成し、その後、すべてをVuexストアに結合します。

次に、アプリケーションの状態を保存する場所を作成します。

プロジェクトのルートディレクトリsrcにあるstoreフォルダは、プロジェクトのセットアップ時に自動的に作成されます。 プロジェクトのsrcディレクトリでstoreフォルダを見つけ、modulesという名前の新しいフォルダを作成します。

mkdir src/store/modules

このフォルダー内に、productおよびcartフォルダーを作成します。

mkdir src/store/modules/product
mkdir src/store/modules/cart

これらは、製品在庫とユーザーのカートのすべての状態ファイルを保持します。 これらの2つのファイルを同時にビルドし、それぞれを別々のターミナルで開きます。 このようにして、ミューテーション、ゲッター、およびアクションを並べて比較することができます。

最後に、productフォルダーにあるindex.jsファイルを開きます。

nano src/store/modules/product/index.js

次のコードを追加して、productItemsを含む状態オブジェクトを作成します。

vuex-shopping-cart / src / store / modules / product / index.js

import axios from 'axios';
const state = {
  productItems: [] 
}

ファイルを保存して開いたままにします。

同様に、新しい端末で、index.jsファイルをcartディレクトリに次のように追加します。

nano src/store/modules/cart/index.js

次に、cartItemsのコードを追加します。

vuex-shopping-cart / src / store / modules / cart / index.js

import axios from 'axios';
const state = {
  cartItems: []
}

このファイルを保存しますが、開いたままにしておきます。

これらのコードスニペットでは、Axiosモジュールをインポートして状態を設定しました。 stateは、コンポーネント間で共有する必要のあるアプリケーションレベルのデータを保持するストアオブジェクトです。

状態を設定したので、ミューテーションに進みます。

突然変異

Mutations は、ストアの状態を変更するメソッドです。 これらは通常、文字列タイプと、状態とペイロードをパラメーターとして受け入れるハンドラーで構成されます。

これで、アプリケーションのすべてのミューテーションを作成します。

stateセクションの直後のproduct/index.jsファイルに次のコードを追加します。

vuex-shopping-cart / src / store / modules / product / index.js

...
const mutations = {
  UPDATE_PRODUCT_ITEMS (state, payload) {
    state.productItems = payload;
  }
}

これにより、productItems配列をpayload値に設定するUPDATE_PRODUCT_ITEMSメソッドを保持するmutationsオブジェクトが作成されます。

同様に、cart/index.jsファイルのstateセクションの直後に次のコードを追加します。

vuex-shopping-cart / src / store / modules / cart / index.js

...
const mutations = {
  UPDATE_CART_ITEMS (state, payload) {
    state.cartItems = payload;
  }
}

これにより、ユーザーのショッピングカートに同様のUPDATE_CART_ITEMSが作成されます。 これは、大文字の変更を参照するFluxアーキテクチャスタイルに従っていることに注意してください。

行動

アクションはミューテーションを処理するメソッドであるため、ミューテーションは他のアプリケーションコードから隔離されます。

product/index.jsで、アプリケーションのすべてのアクションを含むactionsオブジェクトを作成します。

vuex-shopping-cart / src / store / modules / product / index.js

...
const actions = {
  getProductItems ({ commit }) {
    axios.get(`/api/products`).then((response) => {
      commit('UPDATE_PRODUCT_ITEMS', response.data)
    });
  }
}

ここで、getProductItemsメソッドは、前にインストールしたAxiosパッケージを使用して、非同期のGET要求をサーバーに送信します。 リクエストが成功すると、UPDATE_PRODUCT_ITEMSミューテーションが呼び出され、応答データがペイロードとして使用されます。

次に、次のactionsオブジェクトをcart/index.jsに追加します。

vuex-shopping-cart / src / store / modules / cart / index.js

...
const actions = {
  getCartItems ({ commit }) {
    axios.get('/api/cart').then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  addCartItem ({ commit }, cartItem) {
    axios.post('/api/cart', cartItem).then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  removeCartItem ({ commit }, cartItem) {
    axios.delete('/api/cart/delete', cartItem).then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  },
  removeAllCartItems ({ commit }) {
    axios.delete('/api/cart/delete/all').then((response) => {
      commit('UPDATE_CART_ITEMS', response.data)
    });
  }
}

このファイルでは、getCartItemsメソッドを作成します。このメソッドは、非同期のGET要求をサーバーに送信します。 リクエストが成功すると、UPDATE_CART_ITEMSミューテーションが呼び出され、応答データがペイロードとして使用されます。 removeAllCartItemsメソッドでも同じことが起こりますが、サーバーに対してDELETE要求を行います。 removeCartItemおよびaddCartItemメソッドは、DELETEまたはPOST要求を行うためのパラメーターとしてcartItemオブジェクトを受け取ります。 リクエストが成功すると、UPDATE_CART_ITEMSミューテーションが呼び出され、応答データがペイロードとして使用されます。

ES6 destructuring を使用して、commitメソッドをVuexcontextオブジェクトから切り離しました。 これは、context.commitの使用に似ています。

ゲッター

Getters は、アプリケーションストアに対して、コンポーネントに対して計算されたプロパティが何であるかを示します。 それらは、計算された状態データの受信を含むストア状態メソッドから計算された情報を返します。

次に、gettersオブジェクトを作成して、productモジュールのすべての情報を取得します。

vuex-shopping-cart / src / store / modules / product / index.js

...
const getters = {
  productItems: state => state.productItems,
  productItemById: (state) => (id) => {
    return state.productItems.find(productItem => productItem.id === id)
  }
}

ここでは、状態の商品アイテムのリストを返すメソッドproductItemsを作成し、続いてproductItemByIdを作成しました。これは、idによって単一の商品を返す高階関数です。

次に、cart/index.jsgettersオブジェクトを作成します。

vuex-shopping-cart / src / store / modules / cart / index.js

...
const getters = {
  cartItems: state => state.cartItems,
  cartTotal: state => {
    return state.cartItems.reduce((acc, cartItem) => {
      return (cartItem.quantity * cartItem.price) + acc;
    }, 0).toFixed(2);
  },
  cartQuantity: state => {
    return state.cartItems.reduce((acc, cartItem) => {
      return cartItem.quantity + acc;
    }, 0);
  }
}

このスニペットでは、状態のカートアイテムのリストを返すcartItemsメソッドを作成し、続いてチェックアウトに使用できるカートアイテムの合計量の計算値を返すcartTotalを作成しました。 。 最後に、cartQuantityメソッドを作成しました。このメソッドは、カート内のアイテムの数量を再調整します。

モジュールのエクスポート

productおよびcartモジュールの最後の部分は、statemutationsactions、およびgettersをエクスポートします。アプリケーションの他の部分がそれらにアクセスできるようにオブジェクト。

product/index.jsで、ファイルの最後に次のコードを追加します。

vuex-shopping-cart / src / store / modules / product / index.js

...
const productModule = {
  state,
  mutations,
  actions,
  getters
}

export default productModule;

これにより、すべての状態オブジェクトがproductModuleオブジェクトに収集され、モジュールとしてエクスポートされます。

product/index.jsを保存して、ファイルを閉じます。

次に、同様のコードをcart/index.jsに追加します。

vuex-shopping-cart / src / store / modules / product / index.js

...
    const cartModule = {
  state,
  mutations,
  actions,
  getters
}
export default cartModule;

これにより、モジュールがcartModuleとしてエクスポートされます。

ストアの設定

状態、ミューテーション、アクション、およびゲッターがすべて設定された状態で、Vuexをアプリケーションに統合する最後の部分はストアを作成することです。 ここでは、Vuexモジュールを利用して、アプリケーションストアを2つの管理可能なフラグメントに分割します。

ストアを作成するには、storeフォルダーにあるindex.jsファイルを開きます。

nano src/store/index.js

次の強調表示された行を追加します。

vuex-shopping-cart / src / store / index.js

import { createStore } from 'vuex'
import product from'./modules/product';
import cart from './modules/cart';

export default createStore({
  modules: {
    product,
    cart
  }
})

ファイルを保存して、テキストエディタを終了します。

これで、状態管理に必要なメソッドが作成され、ショッピングカート用のストアが作成されました。 次に、データを使用するためのユーザーインターフェイス(UI)コンポーネントを作成します。

ステップ4—インターフェイスコンポーネントの作成

ショッピングカートのストアを設定したので、ユーザーインターフェイス(UI)のコンポーネントの作成に進むことができます。 これには、ルーターにいくつかの変更を加え、ナビゲーションバーのフロントエンドコンポーネントを作成し、製品とカートのリストビューとアイテムビューを作成することが含まれます。

まず、vue-routerの設定を更新します。 Vue CLIツールを使用してアプリケーションをスキャフォールディングするときに、ルーターオプションを選択したことを思い出してください。これにより、Vueがルーターを自動的にセットアップできるようになります。 これで、ルーターを再構成して、後で作成するVueコンポーネントであるCart_List.vueおよびProduct_List.vueのパスを提供できます。

次のコマンドでルーターファイルを開きます。

nano vuex-shopping-cart/src/router/index.js

次の強調表示された行を追加します。

vuex-shopping-cart / src / router / index.js

import { createRouter, createWebHashHistory } from 'vue-router'
import CartList from '../components/cart/Cart_List.vue';
import ProductList from '../components/product/Product_List.vue';

const routes = [
  {
    path: '/inventory',
    component: ProductList
  },
  {
    path: '/cart',
    component: CartList
  },
  {
    path: '/',
    redirect: '/inventory'
  },
]
const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

これにより、商品の/inventoryルートと、カート内の商品の/cartルートが作成されます。 また、ルートパス/を製品ビューにリダイレクトします。

このコードを追加したら、ファイルを保存して閉じます。

これで、UIコンポーネントディレクトリを設定できます。 端末で次のコマンドを実行して、コンポーネントのディレクトリに移動します。

cd src/components

次のコマンドを実行して、コンポーネントのディレクトリの下に3つの新しいサブフォルダを作成します。

mkdir core cart product

coreは、ナビゲーションバーなど、アプリケーションの重要な部分を保持します。 cartproductは、ショッピングカートのアイテムビューとリストビュー、および総在庫を保持します。

coreディレクトリの下で、次のコマンドを実行してNavbar.vueファイルを作成します。

touch core/Navbar.vue

cartディレクトリの下に、ファイルCart_List_Item.vueおよびCart_List.vueを作成します。

touch cart/Cart_List_Item.vue cart/Cart_List.vue

最後に、productディレクトリの下に、次の2つのファイルを作成します。

touch product/Product_List_Item.vue product/Product_List.vue

ファイル構造の概要がわかったので、フロントエンドアプリの個々のコンポーネントの作成に進むことができます。

Navbarコンポーネント

ナビゲーションバーのカートナビゲーションリンクには、カート内のアイテムの数量が表示されます。 Vuex mapGettersヘルパーメソッドを使用して、ストアのゲッターをコンポーネントで計算されたプロパティに直接マッピングし、アプリがストアのゲッターからNavbarコンポーネントにこのデータを取得できるようにします。

navbarファイルを開きます。

nano core/Navbar.vue

コードを次のように置き換えます。

vuex-shopping-cart / src / components / core / Navbar.vue

<template>
    <nav class="navbar" role="navigation" aria-label="main navigation">
      <div class="navbar-brand">
        <a
          role="button"
          class="navbar-burger burger"
          aria-label="menu"
          aria-expanded="false"
          data-target="navbarBasicExample"
        >
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </a>
      </div>
      <div id="navbarBasicExample" class="navbar-menu">
        <div class="navbar-end">
          <div class="navbar-item">
            <div class="buttons">
              <router-link to="/inventory" class="button is-primary">
               <strong> Inventory</strong>
              </router-link>
              <router-link to="/cart"  class="button is-warning">   <p>
    Total cart items:
    <span> {{cartQuantity}}</span> </p>
              </router-link>
            </div>
          </div>
        </div>
      </div>
    </nav>
</template>
<script>
import {mapGetters} from "vuex"
export default {
    name: "Navbar",
    computed: {
    ...mapGetters([
      'cartQuantity'
    ])
  },
  created() {
    this.$store.dispatch("getCartItems");
  }
}
</script>

Vueコンポーネントとして、このファイルはtemplate要素で始まります。この要素はコンポーネントのHTMLを保持します。 このスニペットには、BulmaCSSフレームワークから事前に作成されたスタイルを使用する複数のnavbarクラスが含まれています。 詳細については、Bulmaのドキュメントをご覧ください。

また、router-link要素を使用してアプリを商品やカートに接続し、cartQuantity計算プロパティとして使用して、商品内のアイテム数を動的に追跡しますカート。

JavaScriptはscript要素に保持されます。この要素は、状態管理も処理し、コンポーネントをエクスポートします。 getCartItemsアクションは、ナビゲーションバーコンポーネントが作成されるときにディスパッチされ、サーバーから受信した応答データからのすべてのカートアイテムでストアの状態を更新します。 この後、ストアゲッターは戻り値を再計算し、cartQuantityがテンプレートにレンダリングされます。 作成されたライフサイクルフックにgetCartItemsアクションをディスパッチしない場合、ストアの状態が変更されるまで、cartQuantityの値は0になります。

ファイルを保存して閉じます。

Product_Listコンポーネント

このコンポーネントは、Product_List_Itemコンポーネントの親です。 Product_List_Item(子)コンポーネントに小道具として製品アイテムを渡す責任があります。

まず、ファイルを開きます。

nano product/Product_List.vue

Product_List.vueを次のように更新します。

vuex-shopping-cart / src / components / product / Product_List.vue

<template>
  <div class="container is-fluid">
    <div class="tile is-ancestor">
      <div class="tile is-parent" v-for="productItem in productItems" :key="productItem.id">
      <ProductListItem :productItem="productItem"/>
      </div>
    </div>
  </div>
</template>
<script>
import { mapGetters } from 'vuex';
import Product_List_Item from './Product_List_Item'
export default {
  name: "ProductList",
  components: {
    ProductListItem:Product_List_Item
  },
  computed: {
    ...mapGetters([
      'productItems'
    ])
  },
  created() {
    this.$store.dispatch('getProductItems');
  }
};
</script>

前述のNavbarコンポーネントロジックと同様に、ここではVuex mapGettersヘルパーメソッドがストアゲッターをコンポーネント計算プロパティに直接マップして、ストアからproductItemsデータを取得します。 getProductItemsアクションがディスパッチされ、サーバーから受信した応答データのすべての商品アイテムでストアの状態が更新されます。 この後、ストアゲッターは戻り値を再計算し、productItemsがテンプレートにレンダリングされます。 作成されたライフサイクルフックにgetProductItemsアクションをディスパッチしないと、ストアの状態が変更されるまで、テンプレートに商品アイテムが表示されません。

Product_List_Itemコンポーネント

このコンポーネントは、Product_Listコンポーネントの直接の子コンポーネントになります。 親からproductItemデータを小道具として受け取り、テンプレートにレンダリングします。

Product_List_Item.vueを開きます:

nano product/Product_List_Item.vue

次に、次のコードを追加します。

vuex-shopping-cart / src / components / product / Product_List_Item.vue

<template>
    <div class="card">
      <div class="card-content">
        <div class="content">
          <h4>{{ productItem.title }}</h4>
          <a
            class="button is-rounded is-pulled-left"
            @click="addCartItem(productItem)"
          >
            <strong>Add to Cart</strong>
          </a>
          <br />
          <p class="mt-4">
            {{ productItem.description }}
          </p>
        </div>
        <div class="media">
          <div class="media-content">
            <p class="title is-6">{{ productItem.owner }}</p>
            <p class="subtitle is-7">{{ productItem.email }}</p>
          </div>
          <div class="media-right">
            <a class="button is-primary is-light">
              <strong>$ {{ productItem.price }}</strong>
            </a>
          </div>
        </div>
      </div>
    </div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
  name: "ProductListItem",
  props: ["productItem"],
  methods: {
    ...mapActions(["addCartItem"]),
  },
};
</script>

以前のコンポーネントで使用されていたmapGettersヘルパー関数に加えて、Vuexには、コンポーネントメソッドをストアのアクションに直接マップするmapActionsヘルパー関数も用意されています。 この場合、mapActionヘルパー関数を使用して、コンポーネントメソッドをストアのaddCartItemアクションにマップします。 これで、カートにアイテムを追加できます。

ファイルを保存して閉じます。

Cart_Listコンポーネント

このコンポーネントは、カートに追加されたすべての製品アイテムを表示し、カートからすべてのアイテムを削除する役割を果たします。

このコンポーネントを作成するには、最初にファイルを開きます。

nano cart/Cart_List.vue

次に、Cart_List.vueを次のように更新します。

vuex-shopping-cart / src / components / cart / Cart_List.vue

<template>
  <div id="cart">
    <div class="cart--header has-text-centered">
      <i class="fa fa-2x fa-shopping-cart"></i>
    </div>
    <p v-if="!cartItems.length" class="cart-empty-text has-text-centered">
      Add some items to the cart!
    </p>
    <ul>
      <li class="cart-item" v-for="cartItem in cartItems" :key="cartItem.id">
          <CartListItem :cartItem="cartItem"/>
      </li>
      <div class="notification is-success">
        <button class="delete"></button>
        <p>
          Total Quantity:
          <span class="has-text-weight-bold">{{ cartQuantity }}</span>
        </p>
      </div>
      <br>
    </ul>
    <div class="buttons">
    <button :disabled="!cartItems.length" class="button is-info">
      Checkout (<span class="has-text-weight-bold">${{ cartTotal }}</span>)
    </button>
     
 <button class="button is-danger is-outlined" @click="removeAllCartItems">
    <span>Delete All items</span>
    <span class="icon is-small">
      <i class="fas fa-times"></i>
    </span>
  </button>
       </div>
  </div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
import CartListItem from "./Cart_List_Item";
export default {
  name: "CartList",
  components: {
    CartListItem
  },
  computed: {
    ...mapGetters(["cartItems", "cartTotal", "cartQuantity"]),
  },
  created() {
    this.$store.dispatch("getCartItems");
  },
  methods: {
    ...mapActions(["removeAllCartItems"]),
  }
};
</script>

このコードは、テンプレートで v-ifステートメントを使用して、カートが空の場合に条件付きでメッセージをレンダリングします。 それ以外の場合は、カートアイテムのストアを繰り返し処理し、ページにレンダリングします。 また、cartItemscartTotal、およびcartQuantityゲッターをロードしてデータプロパティを計算し、removeAllCartItemsアクションを実行しました。

ファイルを保存して閉じます。

Cart_List_Itemコンポーネント

このコンポーネントは、Cart_Listコンポーネントの直接の子コンポーネントです。 cartItemデータを親から小道具として受け取り、テンプレートにレンダリングします。 また、カート内のアイテムの数量を増減する役割も果たします。

ファイルを開きます。

nano cart/Cart_List_Item.vue

Cart_List_Item.vueを次のように更新します。

vuex-shopping-cart / src / components / cart / Cart_List_Item.vue

<template>
  <div class="box">
    <div class="cart-item__details">
      <p class="is-inline">{{cartItem.title}}</p>
      <div>
        <span class="cart-item--price has-text-info has-text-weight-bold">
          ${{cartItem.price}} X {{cartItem.quantity}}
        </span>
        
        <span>
          <i class="fa fa-arrow-circle-up cart-item__modify" @click="addCartItem(cartItem)"></i>
          <i class="fa fa-arrow-circle-down cart-item__modify" @click="removeCartItem(cartItem)"></i>
        </span>
      </div>
      
    </div>
  </div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
  name: 'CartListItem',
  props: ['cartItem'],
  methods: {
    ...mapActions([
      'addCartItem',
      'removeCartItem'
    ])
  }
}
</script>

ここでは、mapActionヘルパー関数を使用して、コンポーネントメソッドをストア内のaddCartItemおよびremoveCartItemアクションにマップしています。

ファイルを保存して閉じます。

最後に、App.vueファイルを更新して、これらのコンポーネントをアプリに取り込みます。 まず、プロジェクトのルートフォルダに戻ります。

cd ../..

次に、ファイルを開きます。

nano src/App.vue

内容を次のコードに置き換えます。

vuex-shopping-cart / src / App.vue

<template>
  <div>
    <Navbar/>
    <div class="container mt-6">
      <div class="columns">
        <div class="column is-12 column--align-center">
          <router-view></router-view>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import Navbar from './components/core/Navbar'
export default {
  name: 'App',
  components: {
    Navbar
  }
}
</script>
<style>
html,
body {
  height: 100%;
  background: #f2f6fa;
}
</style>

App.vueは、Vueコンポーネントファイル形式で定義されたアプリケーションのルートです。 変更を加えたら、ファイルを保存して閉じます。

このステップでは、ナビゲーションバー、商品在庫、およびショッピングカートのコンポーネントを作成して、ショッピングカートアプリのフロントエンドを設定します。 また、前の手順で作成したストアアクションとゲッターも使用しました。 次に、アプリケーションを起動して実行します。

ステップ5—アプリケーションの実行

アプリの準備ができたので、開発サーバーを起動して最終製品を試すことができます。

フロントエンドプロジェクトのルートで次のコマンドを実行します。

npm run serve

これにより、http://localhost:8080でアプリを表示できる開発サーバーが起動します。 また、バックエンドが別の端末で実行されていることを確認してください。 これを行うには、cart-backendプロジェクトで次のコマンドを実行します。

node server

バックエンドとフロントエンドが実行されたら、ブラウザでhttp://localhost:8080に移動します。 機能しているショッピングカートアプリケーションが見つかります。

結論

このチュートリアルでは、データ管理にVue.jsとVuexを使用してオンラインショッピングカートアプリを作成しました。 これらの手法は、eコマースショッピングアプリケーションの基盤を形成するために再利用できます。 Vue.jsの詳細については、Vue.jsトピックページをご覧ください。