LoginSignup
8
6

More than 3 years have passed since last update.

Vuexで状態管理しながらメモアプリを作ってみた

Last updated at Posted at 2019-07-30

メモアプリを Vuex で管理する

index.vue ファイルとコンポーネントだけで作れる簡単なアプリですが、 Vuex による状態管理まで理解したかったので今回 Vuex を取り入れることにしました。
小さいアプリケーションだとデータの流れが分かりやすいので、これから Vuex を学びたい人におすすめです。

前提条件

  • Nuxt.js をインストール済 バージョン 6.4.1 (npm -vで確認)

  • Nuxt.js 公式ドキュメントのチュートリアルを参考に、新しいプロジェクト名で 「create-nuxt-app」 の設定をします。作業はターミナル上で、ルートで行います(新しいプロジェクト名のディレクトリがルートに出来上がります)。これで簡単に開発の準備が整います。

  • 何もない状態(スクラッチ)から始める場合も上記ドキュメントの中に、続けて記載がありますのでそちら参考にしてみてください。

ディレクトリができ、エディタを開くと↓写真のようなフォルダ一覧があります。http://llocalhost:3000で確認すると、 pages フォルダの中の index.vue の内容になっています。 h1 タグのプロジェクト名の下に自動で description の文字が入っている場合があるので、変更するときは package.json を編集します。(写真の下から3番目)
スクリーンショット 2019-07-30 13.00.22.png

Flux の考え方 → Vuex を利用する場面

Flux についての説明は省きますが、 Vuex とは、ドキュメントによると

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。

Vuex は一箇所にアプリケーションの状態(ステート)がまとまっています。つまり、"Single source of truth"を保てます。情報源を信頼できるものにしようという事で、下記のように流れは一方向です。
State を変更できるのは Mutations のみであり、 Actions は「コミット」しかできません。

スクリーンショット 2019-07-30 16.23.31.png
スクリーンショット 2019-07-30 16.25.52.png

必要なファイルを揃える

さっそく作っていきますが、必要なファイルを3つ用意します。ファイルは template タグでくるみます。私のエディタは Emmet が入っておりscaffoldと入力するとテンプレートが用意されます。

  • pages/index.vue (最初に既にあるはずです)
  • components/MemoList.vue (文字はパスカルケースで作成/ 再利用可能な部品のようなもので Model 。)
  • store/memo.js ( state, mutations, actionsを今後記述します)

フォームを作る

input タグを作るときは、v-modelで Model, View の双方向バインディングを可能にします。片方変更したら、( input の中身について DOM やデータに変更があれば)もう片方も同じように変更されます。
@clickとはv-onのイベントハンドラの省略。
props でコンポーネントに値を渡す際は、ケバブケースmemo-contentsで渡します。コンポーネント側で受け取る時の属性はキャメルケース。

index.vue
<div class="input_container">
  <p>
    <input
      v-model="inputText" 
      type="text"
     >
  </p>
  <button @click="addMemo()">Save</button>
</div>

<!-- コンポーネントに分けた部分について記述-->
<!-- memo-contents が属性で、" memoContents "はここでは変数です-->
<!-- @delete は、ただのきっかけで memo.js アクションの deleteMemo メソッドに伝えているだけ-->
<memo-list
  :memo-contents="memoContents"
  @delete="deleteMemo()"
/>

コンポーネントを作る

input 入力したものは一つ一つがメモリストです。何度も使うのでコンポーネントとして MemoList.vue ファイルに記述します。

ここでulタグの中でv-forを用いてリストを複数生成しています。
先にscriptタグのprops:を見てください。memoContentsがあり、ここで配列を作っています。
先程のリストの複数生成時にはここのことを指しており、 memoContents に入っている分だけインデックスを付けてアイテム( リスト )を生成します。

Memolist.vue
<template>
  <div>
    <ul
      v-for="(item, index) in memoContents"
      :key="index"
    >
      <li>
        <span>{{ item.text }}</span> <!-- ここについては後ほど -->
        <span>{{ item.date | dateConvert}}</span> <!-- ここについては後ほど -->
        <button @click="handleDelete(index)">x</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {  //DOMに描き出すためにこのように書きます
    props: {    //親である index.vue から受け取るときはこの中に入ります
      memoContents: {  // memoContents として受け取り、値を一つずつ配列[]に入れます
        type: Array,
        default: () => []
      }
    },

index.vue に追記

コンポーネントを組み込む際には import する必要があります。
まだ Vuex に関する memo.js は記述していませんが、ここで呼び出します。methodsの中addMemo()については連想配列の階層がコンポーネントで書いたものと一致する必要があります。
computedでは後述の memo.js の state の memoContents を参照。若干ややこしいですが、後述の memo.js と照らし合わせてみてください。

index.vue
<script>
  // MemoList.vue を import したら、 exportdefault の中に compornent として記述
  import MemoList from '~/components/MemoList'
  import { mapState, mapActions } from 'vuex' // このように記述する

  export default {
    components: {  //ここでも描き出すためにMemoListを書きます
      MemoList
    },
    data: () => ({
      inputText: ""
    }),
    // 算出プロパティといいますが、 DOM へ表示されるものが computed です
    computed: {
      ...mapState({
        // どう index.vue で呼ぶか宣言して、 state の中の memo.js の中の memoContents を参照
        memoContents: state => state.memo.memoContents
      }),
      // ...mapState('memo', ['memoContents'])とも書けます
    },
    methods: {
      ...mapActions('memo', ['addMemoContents','deleteMemoContents']),
      addMemo() {
        // methods 内の関数として呼び出し
        this.addMemoContents({  //ここがコンポーネント側で{{ item.text }}で書いたところと階層が一致するはず
          text: this.inputText,
          date: new Date()
        })
        this.inputText = ""
      },
      // memoContens を消すものなので deleteMemo メソッドは index.vue にあるべき
      // 子( MemoList.vue からの)の index を受け取っている
      deleteMemo(index) {
        this.deleteMemoContents(index)
      }
    }
  }
</script>

MemoList.vue に追記

ここで重要なものがもうひとつ、子から親へ値を渡す$emitです。

MemoList.vue
<script>
  export default {   
  // component 内で完結するもの
    filters: {
      dateConvert: function (value) {
        const date = new Date(value)
        return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
      }
    },
  // 親のメソッドを発火させるためだけのもの
  methods: {
    //上記のクリックイベントの index を受け取って $emit で親に受け渡す
    handleDelete(index) {
      this.$emit('delete', index)
    }
  }

memo.js に記述

ようやくここまで来ました。今回重要なポイントは、「 mutations 以外は直接 state とやり取りしない」ことです。mutations が唯一 state を更新できるという事を踏まえて見ていきます。

memo.js

// state  memoContentsが入っている状態
export const state = () => ({
  memoContents: []
})

//mutations データを更新できる
export const mutations = {
  //引数に state と書く。 memo は actions から渡される
  updateMemoContents(state, memo) {
    state.memoContents.push(memo)
  },
  deleteMemoItem(state, index) {
    state.memoContents.splice(index,1)
  }
}

// actions ・・ mutations を発動する
export const actions = {
  // { commit }と書くことで mutations に渡せる
  // index.vue からの memoDate を受け取ってから mutations に commit
  addMemoContents({ commit }, memoData) {
    commit('updateMemoContents', memoData)
  },
  deleteMemoContents({ commit }, index) {
    commit('deleteMemoItem', index)
  }
}

まとめ

今回初めて Vuex を勉強しましたが、まだまだ理解が足りない部分が多く、冗長になってしまいました。 Vuex を使うべきアプリではありませんが、小さいアプリで全体像が捉えられたのは良かったです。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6