Vue3とVuexでショッピングカートを作成する方法
著者は、 Open Source Initiative を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Vue.js は、パフォーマンスが高く、プログレッシブ Javascriptフレームワークです。 これはGitHubで人気のあるフレームワークであり、活発で役立つコミュニティがあります。
Vue Webフレームワークの機能を示すために、このチュートリアルでは、eコマースアプリのショッピングカートの作成について説明します。 このアプリは、商品情報を保存し、顧客が後でチェックアウトするために購入したい商品を保持します。 情報を保存するには、Vue.jsで広く使用されている状態管理ライブラリVuexを調べます。 これにより、ショッピングカートアプリケーションがデータをサーバーに永続化できるようになります。 また、Vuexを使用して非同期タスク管理を処理します。
チュートリアルを終了すると、次のような機能するショッピングカートアプリケーションが作成されます。
前提条件
- Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.22.0およびnpmバージョン6.14.6でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- また、JavaScript、HTML、およびCSSの基本的な知識も必要です。これは、HTMLシリーズを使用してWebサイトを構築する方法、CSSシリーズを使用してWebサイトを構築する方法にあります。 、およびJavaScriptでコーディングする方法。
ステップ1—VueCLIを使用したアプリケーションのセットアップ
バージョン4.5.0以降、 Vue CLI には、新しいプロジェクトを作成するときにVue3プリセットを選択するための組み込みオプションが用意されています。 最新バージョンのVueCLIを使用すると、Vue 3をそのまま使用して、既存のVue2プロジェクトをVue3に更新できます。 このステップでは、Vue CLIを使用してプロジェクトを作成し、フロントエンドの依存関係をインストールします。
まず、ターミナルから次のコマンドを実行して、最新バージョンのVueCLIをインストールします。
npm install -g @vue/cli
これにより、VueCLIがシステムにグローバルにインストールされます。
注:一部のシステムでは、npmパッケージをグローバルにインストールすると、アクセス許可エラーが発生し、インストールが中断される可能性があります。 sudo
をnpm 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 version
、Router
、および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オブジェクトとid
、title
、description
、price
、image_tag
、quantity
プロパティ。 このコードは、カートがすでに存在するかどうかもチェックして、繰り返し商品のリクエストが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.js
にgetters
オブジェクトを作成します。
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
モジュールの最後の部分は、state
、mutations
、actions
、および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
は、ナビゲーションバーなど、アプリケーションの重要な部分を保持します。 cart
とproduct
は、ショッピングカートのアイテムビューとリストビュー、および総在庫を保持します。
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
ファイル構造の概要がわかったので、フロントエンドアプリの個々のコンポーネントの作成に進むことができます。
ナビゲーションバーのカートナビゲーションリンクには、カート内のアイテムの数量が表示されます。 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ステートメントを使用して、カートが空の場合に条件付きでメッセージをレンダリングします。 それ以外の場合は、カートアイテムのストアを繰り返し処理し、ページにレンダリングします。 また、cartItems
、cartTotal
、および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トピックページをご覧ください。