kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Vuex でショッピングカートを実装できる無料コース「Vuex for Everyone」を受講した

Vue School の無料コース「Vuex for Everyone」を受講した.Vue.js 初心者でも受講できるレベルになっているし,今まで Vue.js を書いたことはあるけど,Vuex はまだ使ったことがないという人にもオススメできる.進め方にもよるけど,写経するとしても2,3時間あれば終わる.

Vuex for Everyone

今回受講した「Vuex for Everyone」の題材は「shopping-cart」で,ステップバイステップに実装を進めていく.動作確認をしながら,機能を追加したり,リファクタリングをしたりする.

vueschool.io

動画を見るとわかるけど,とにかく実装スピードが早いので,写経する場合は何度も戻して見直す必要がある.ただ困ったときは Vue School の GitHub にセッションごとにコミットされているので,それを活用すればオッケー.

github.com

セッション一覧

  • Introduction
    • Meet Vuex ⏲ 2:46
    • Create a new project with vue-cli ⏲ 3:45
    • Install and use Vuex ⏲ 1:57
  • Vuex Options
    • Vuex Mutations ⏲ 3:22
    • Vuex Getters ⏲ 1:57
    • Vuex Actions ⏲ 4:25
    • Store Access from all Components ⏲ 0:53
  • Shopping Cart Features
    • Add products to the cart ⏲ 5:08
    • Vuex Mutation History and Vue Devtools ⏲ 2:46
    • Cart items and total ⏲ 4:32
    • Checkout ⏲ 3:49
  • Advanced Vuex Options
    • Dynamic Vuex Getters ⏲ 2:31
    • Vuex Map Helpers ⏲ 8:04
    • Split Vuex Store in Multiple Files ⏲ 1:11
    • Vuex Modules ⏲ 7:23
    • Namespaced Vuex Modules ⏲ 6:27

f:id:kakku22:20180915094327p:plain

(受講画面例)

Meet Vuex

  • Vuex とは何か?
  • なぜ Vuex が必要なのか?

Create a new project with vue-cli

Vue School のボイラープレートから「shopping-cart」プロジェクトを作成する.

$ npm install -g vue-cli
$ npm install -g yarn

$ vue init vueschool/webpack-template shopping-cart

? Project name shopping-cart
? Project description A Vue.js project
? Author kakakakakku <y.yoshida22@gmail.com>
? Vue build runtime
? Install vue-router? No
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? No
? Setup e2e tests with Nightwatch? No

   vue-cli · Generated "shopping-cart".

   To get started:

     cd shopping-cart
     npm install
     npm run dev

   Documentation can be found at https://vuejs-templates.github.io/webpack

$ cd shopping-cart
$ yarn
$ yarn dev

次に Vuex の GitHub にあるサンプルを参考に,ProductList コンポーネントを実装する.API 部分はモックになっている.

const _products = [
    {"id": 1, "title": "iPad 4 Mini", "price": 500.01, "inventory": 2},
    {"id": 2, "title": "H&M T-Shirt White", "price": 10.99, "inventory": 10},
    {"id": 3, "title": "Charli XCX - Sucker CD", "price": 19.99, "inventory": 5}
  ]

この時点で,3商品を表示することができる.

f:id:kakku22:20180915093954p:plain

(実装画面)

Install and use Vuex

ここで Vuex をインストールする.

$ yarn add vuex

そして,新しく store/index.js を作成する.ここで,さっそく Vuex で重要なコンセプトが出てくる.

  • state
  • getters
  • actions
  • mutations

Vuex Mutations

ここでは,実際に mutations を実装する.ハンドラ関数の第一引数として state を指定し,追加でペイロードとして products を指定している.

// shopping-cart/src/store/index.js
mutations: {
  setProducts (state, products) {
    // update products
    state.products = products
  }
}

Vuex Getters

getters の実装はシンプルで,ストアからフィルタして取得できる.実際にモックの1商品の在庫を0にして,動作確認をした.

// shopping-cart/src/store/index.js
getters: { // = computed properties
  availableProducts (state, getters) {
    return state.products.filter(product => product.inventory > 0)
  }
}

f:id:kakku22:20180915094003p:plain

(実装画面)

Vuex Actions

この時点だと,まだコンポーネントから直接 Ajax で API を呼び出しているので,ベストプラクティスではなく,actions を使って解決するとのことだった.最初は,そのまま mutations をコミットすれば良いのではないかと思っていたけど,mutations は同期,actions は非同期なので,ここでは actions を使って,mutations をコミットする必要性を学べた.そして,API を呼び出しているときのローディング画像もここで実装した.

// shopping-cart/src/store/index.js
actions: { // = methods
  fetchProducts ({commit}) {
    return new Promise((resolve, reject) => {
      // make the call
      // call setProducts mutation
      shop.getProducts(products => {
        commit('setProducts', products)
        resolve()
      })
    })
  }
},

actions を呼び出すときは .dispatch を使う.そして actions の中で Promise を実装しているので,ここでは .then で受けて,ローディング画像を消している.

// shopping-cart/src/components/ProductList.vue
created () {
  this.loading = true
  store.dispatch('fetchProducts')
    .then(() => this.loading = false)
}

f:id:kakku22:20180915094012p:plain

(実装画面)

Store Access from all Components

全てのコンポーネントで store を import しなくても良いように,ルートコンポーネントに注入し,各コンポーネントでは this.$store で参照できるようにした.

// shopping-cart/src/main.js
new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

Add products to the cart

次に商品をカートに入れられるようにストアにカートを実装した.ここまでの理解もあったので actions と mutations の実装も問題なかった.

f:id:kakku22:20180915094021p:plain

(実装画面)

Vuex Mutation History and Vue Devtools

Chrome 拡張を使って Vuex の状態を確認した.状態を Time Travel して戻すことができるのは,デバッグするときに便利そうだった.

f:id:kakku22:20180915094029p:plain

(実装画面)

Cart items and total

Vuex の GitHub から Filters 用の currency.js をコピーして,カートの実装を追加した.これで,カートの中に何が何個入っていて,総額も確認できるようになった.

f:id:kakku22:20180915094037p:plain

(実装画面)

Checkout

次は,購入できるようにチェックアウトボタンを実装した.実際にチェックアウトはできないので,ボイラープレートに入っている実装で,ランダムで成功したり,失敗したりするようになっている.

buyProducts (products, cb, errorCb) {
  setTimeout(() => {
    // simulate random checkout failure.
    (Math.random() > 0.5 || navigator.userAgent.indexOf('PhantomJS') > -1)
      ? cb()
      : errorCb()
  }, 100)
}

actions から,成功時の mutations と 失敗時の mutations を送っている.

// shopping-cart/src/store/index.js
checkout ({state, commit}) {
  shop.buyProducts(
    state.cart,
    () => {
      commit('emptyCart')
      commit('setCheckoutStatus', 'success')
    },
    () => {
      commit('setCheckoutStatus', 'fail')
    }
  )
}

Dynamic Vuex Getters

在庫がないときに商品名が消えてしまうのは微妙なので,Add to cart ボタンを disable にする実装をした.

f:id:kakku22:20180915094046p:plain

(実装画面)

Vuex Map Helpers

ここからは,Vuex のお作法でリファクタリングをしていく.まずは Map Helpers で state / getters / actions の宣言をシンプルにするために mapState / mapGetters / mapActions を宣言し,スプレッド演算子とともに,その中に呼び出し名を宣言できる.

// shopping-cart/src/components/ProductList.vue
computed: {
  ...mapState({
    products: state => state.products
  }),
  ...mapGetters({
    productIsInStock: 'productIsInStock'
  })
}

Split Vuex Store in Multiple Files

次は store/index.js の肥大化を防ぐために,actions だけを store/actions.js に切り出した.ただし,ここでは切り出せることを学ぶだけで,実際には次に出てくる Vuex Modules で書き直すことになる.

Vuex Modules

モジュールも Vuex のコンポーネントで,ストアごとに切り出していくようなイメージになる.今回は store/modules/cart.jsstore/modules/products.js に切り出した.モジュールごとに state / getters / mutations / actions を持っているので,わかりやすいし,テストコードも書きやすそう.結果的に store/index.js に実装はなくなり,とにかく薄くなった.

// store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
import actions from './actions'
import cart from './modules/cart'
import products from './modules/products'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    cart,
    products
  },

  state: { // = data
  },

  getters: { // = computed properties
  },

  actions,

  mutations: {
  }
})

Namespaced Vuex Modules

最後は namespaced で,モジュールで namespaced: true を指定することにより,コンポーネント側で mapActions に名前空間を指定できるようになる.メソッド名にストア名を含めて冗長になっている場合など,シンプルに書けるようになる.

まとめ

  • Vue School の無料コース「Vuex for Everyone」を受講した
  • ショッピングカートを実装しながら,Vuex の基礎を学べた
  • 動画だと理解できなかった部分などは公式ドキュメント(日本語)を参考にした

vuex.vuejs.org

写経した GitHub リポジトリ

github.com

他にもある Vue School の無料コース

過去に受講した Firebase Realtime Database の無料コースも,Firebase Authentication の無料コースも良かった.

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com