Nuxt.jsを使ってみた

#前提
Node.jsインストール済み

#本題

```
$ npx create-nuxt-app hello
```
npxは、npmパッケージのダウンロードと実行を１度に行います。
※ホットリローディングに対応しているのはdev（開発環境）のみ。
npm run build、npm run startは本番環境にアップロードする前などに使用するもので開発時には基本的には使わない。

#マスタッシュ構文

```page/index.vue
<template>
  <div class="container">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello Nuxt.js!"
    };
  }
};
</script>

<style>
</style>
```

#ページ遷移
pageフォルダ内に新規ファイルを作成

```page/test.vue
<template>
  <selection class="container">
    <h1>Test</h1>
    <hr />
    //追記
    <router-link to="/">Test Page</router-link>
  </selection>
</template>
```
Vue Routerを使用。
Nuxt.jsでは自動で設定してくれている。

```page/index.vue
<template>
  <div class="container">
    <p>{{ message }}</p>
    <hr />
    //追記
    <router-link to="/test">Top Page</router-link>
  </div>
</template>
```

##パラメーターのバリデーション
数字出ないといけない場合

```
<script>
export default {
  data() {
    return {
      message: 'user/_id.vueを表示中'
    }
  },
  validate({ params }) {
    return /^\d+$/.test(params.id)
  }
};
</script>
```

##エラーページ
404の場合

```layouts/error.vue
<template>
  <div>
    <h1 v-if="error.statusCode === 404">Error! 404</h1>
  </div>
</template>

<script>
export default {
  props: ['error']
};
</script>
```
※エラーページに<nuxt />は含めてはいけない。


##HTMLヘッダーのカスタマイズ
ロゴを変える場合

```nuxt.config.js
head: {
  //省略
  link: [
    { rel: 'stylesheet', href: 'URL'}
  ]
},
//省略
```

```pages/index.vue
<style>
.title {
  font-family: '変更',
  //省略
}
</style>
```

##非同期通信
axiosインストール

```
$ npm install axios
```
データはJSONPlaceholderを使用。
/usersデータを使用。

###データ取得

```pages/index.vue
<template>
  <section class="container">
    <div>
      <ul>
        <li v-for="user in users" :key="user.id">
          {{ user.id }}, {{ user.name }}, {{ user.company.name }}
        </li>
      </ul>
    </div>
  </section>
</template>

<script>
const axios = require('axios')
let url = '/userのURLを添付'

export default {
  asyncData({ params, error }) {
    return axios.get(url)
      .then((res) => {
        return { users: res.data }
      })
      //エラー処理
      .catch((e => {
        error({ users: e.response.status, message: e.message })
      }))
  }
}
</script>
```
asyncDataはNuxt.jsが用意しているメソッド。
コンポーネントを初期化する前に非同期の処理を行えるようにするメソッド。

##画像の表示
画像をassetsディレクトリ配下に設置。

```pages/index.vue
<template>
  <section class="container">
    <div>
      <img src="~/assets/〇〇.jpg">
    </div>
  </section>
</template>

<script>
export default {
}
</script>
```

##ストア作成
storeディレクトリ配下にindex.jsを作成。

```store/index.js
import Vuex from "vuex";

const createStore = () => {
  return new Vuex.Store({
    state: function() {
      return {
        message: "Hello Vuex!"
      };
    }
  });
};

export default createStore;
```

```pages/index.vue
<template>
  <div class="container">
    <div>
      <p>{{ $store.state.message }}</p>
    </div>
  </div>
</template>

<script>
export default {};
</script>
```

###ミューテーションの利用

```store/index.js
//省略

const createStore = () => {
  return new Vuex.Store({
    state: function() {
      return {
        message: "Hello Vuex!"
      };
    },
    mutations: {
      updateMessage: function(state) {
        state.message = "Updated!";
      }
    }
  });
};
//省略
```

```pages/index.vue
<template>
  <div class="container">
    <div>
      <p>{{ $store.state.message }}</p>
      <button @click="$store.commit('updateMessage')">Update</button>
    </div>
  </div>
</template>

<script>
export default {};
</script>
```
ミューテーションの機能をコンポーネントから呼び出すには、commitメソッドを利用する。

###ミューテーションへ値を渡す

```pages/index.vue
<template>
  <div class="container">
    <div>
      <p>{{ $store.state.message }}</p>
      <button @click="$store.commit('updateMessage', 'Commit with payload')">Update</button>
    </div>
  </div>
</template>
```

```store/index.js
const createStore = () => {
  return new Vuex.Store({
    state: function() {
      return {
        message: "Hello Vuex!"
      };
    },
    mutations: {
      updateMessage: function(state, payload) {
        state.message = payload;
      }
    }
  });
};
```
ミューテーションへ値を渡したい時は、commitメソッドの第二引数に渡したい値を入れ、
ミューテーション側（index.js）の第二引数で値を受け取り利用する。

###アクションの利用
これまでステートの値を操作するまでにアクションを飛ばし、ミューテーションを呼んでいたため、アクションを経由するようにする。

```pages/index.vue
<template>
  <div class="container">
    <div>
      <p>{{ $store.state.message }}</p>
      <button @click="$store.dispatch('updateMessageAction', 'Dispatch with payload')">Dispatch</button>
    </div>
  </div>
</template>
```

```store/index.js
const createStore = () => {
  return new Vuex.Store({
    state: function() {
      return {
        message: "Hello Vuex!"
      };
    },
    mutations: {
      updateMessage: function(state, payload) {
        state.message = payload;
      }
    },
    actions: {
      updateMessageAction(context, payload) {
        context.commit("updateMessage", payload);
      }
    }
  });
};
```

###ストアのモジュールモードの利用
・クラシックモード
　１つのファイル（index.js）に記述
・モジュールモード
　複数のファイルに記述

●モジュールモード動作の条件
・index.jsがストアオブジェクトをexportしない
・または、index.jsがstoreフォルダ配下に存在しない

storeディレクトリ配下にhello.jsを作成。

```store/hello.js
export const state = () => ({
  message: "Hello Vuex!"
});

export const mutations = {
  updateMessage: function(state, payload) {
    state.message = payload;
  }
};

export const actions = {
  updateMessageAction(context, payload) {
    context.commit("updateMessage", payload);
  }
};
```

```pages/index.vue
<template>
  <div class="container">
    <div>
      <p>{{ $store.state.hello.message }}</p>
      <button
        @click="$store.dispatch('hello/updateMessageAction', 'Dispatch with payload')"
      >Dispatch</button>
    </div>
  </div>
</template>
```
store/index.jsは削除。


#SPA開発

##firebaseインストール

```
$ npm install --save firebase@6.2.4

$ npm install --save vuexfire@3.0.1
```

##環境変数の設定

```
$ npm install --save @nuxtjs/dotenv@1.3.0
```

.envファイルを作成。

```.env
FIREBASE_PROJECT_ID = 'IDを入力'
```

```nuxt.config.js
/*
   ** Nuxt.js modules
   */
  modules: ["@nuxtjs/dotenv"],
```

##Firebaseとの連携
pluginsにfirebase.jsファイルを作成。

```plugins/firebase.js
import firebase from "firebase";

const config = {
  projectId: process.env.FIREBASE_PROJECT_ID
};

if (!firebase.apps.length) {
  firebase.initializeApp(config);
}

export default firebase;
``

##ストアの作成
storeフォルダ配下にindex.jsを作成。

```store/index.js
import { vuexfireMutations } from "vuexfire";

export const mutations = {
  ...vuexfireMutations
};
```

storeフォルダ配下にlist.jsを作成。

```store/list.js
//firebaseの初期化設定ファイル
import firebase from "~/plugins/firebase";
//vuexfireのfirestoreActionをインポート
import { firestoreAction } from "vuexfire";

//データベース設定
const db = firebase.firestore();
const listsRef = db.collection("lists");

export const state = () => ({
  lists: []
});

export const actions = {
  //FirestoreActionを呼び出す
  init: firestoreAction(({ bindFirestoreRef }) => {
    bindFirestoreRef("lists", listsRef);
  }),
  add: firestoreAction((context, name) => {
    //未入力でなければ下記内容のデータをfirebaseへ保存
    if (name.trim()) {
      listsRef.add({
        name: name,
        done: false,
        created: firebase.firestore.FieldValue.serverTimestamp()
      });
    }
  }),
  remove: firestoreAction((context, id) => {
    listsRef.doc(id).delete();
  }),
  toggle: firestoreAction((context, todo) => {
    listsRef.doc(list.id).update({
      done: !list.done
    });
  })
};
```

##コンポーネントの作成
pagesフォルダ配下にlists.vueを作成。

```pages/lists.vue
<template>
  <div>
    <div class="form">
      <form @submit.prevent="add">
        <input v-model="name" />
        <button>Add</button>
      </form>
    </div>
  </div>
</template>

<script>
import moment from "moment";

export default {
  data: function() {
    return {
      name: "",
      done: false
    };
  },
  created: function() {
    this.$store.dispatch("lists/init");
  },
  methods: {
    add() {
      this.$store.dispatch("lists/add", this.name);
      this.name = "";
    }
  }
};
</script>
```
firebaseに保存されていればOK。

##listsリストを表示
保存したデータを取り出す。

```pages/lists.vue
<template>
  <div>
    <!-- 追記 -->
    <ul>
      <li v-for="list in lists" :key="list.id">
        {{ list.done }} {{ list.name }} {{ list.created.toDate() | dateFilter }}
      </li>
    </ul>
    <div class="form">
      <form @submit.prevent="add">
        <input v-model="name" />
        <button>Add</button>
      </form>
    </div>
  </div>
</template>

<script>
import moment from "moment";

export default {
  data: function() {
    return {
      name: "",
      done: false
    };
  },
  created: function() {
    this.$store.dispatch("lists/init");
  },
  methods: {
    add() {
      this.$store.dispatch("lists/add", this.name);
      this.name = "";
    }
  },
  computed: {
    lists() {
      return this.$store.state.lists.lists;
    }
  },
  filters: {
    dateFilter: function(date) {
      return moment(date).format("YYYY/MM/DD HH:mm:ss");
    }
  }
};
</script>
```

##削除機能

```pages/lists.vue
<template>
  <div>
    <ul>
      <li v-for="list in lists" :key="list.id">
        {{ list.done }} {{ list.name }} {{ list.created.toDate() | dateFilter }}
        //追記
        <button @click="remove(liste.id)">×</button>
      </li>
    </ul>
    <div class="form">
      <form @submit.prevent="add">
        <input v-model="name" />
        <button>Add</button>
      </form>
    </div>
  </div>
</template>

<script>
//省略
export default {
  //省略
  methods: {
    add() {
      this.$store.dispatch("lists/add", this.name);
      this.name = "";
    },
    //追記
    remove(id) {
      this.$store.dispatch("lists/remove", id);
    }
  },
  //省略
};
</script>
```


##完了・未完了チェックボックス

```pages/lists.vue
<template>
  <div>
    <ul>
      <li v-for="list in lists" :key="list.id">
        //追記
        <span v-if="list.created">
          <input type="checkbox" :checked="list.done" @change="toggle(list)" />
          <span :class="{ done: list.done }">{{ list.name }} {{ list.created.toDate() | dateFilter }}</span>
          <button @click="remove(list.id)">×</button>
        </span>
      </li>
    </ul>
    <div class="form">
      <form @submit.prevent="add">
        <input v-model="name" />
        <button>Add</button>
      </form>
    </div>
  </div>
</template>

<script>
//省略
export default {
  //省略
  methods: {
    add() {
      this.$store.dispatch("lists/add", this.name);
      this.name = "";
    },
    remove(id) {
      this.$store.dispatch("lists/remove", id);
    },
    toggle(list) {
      this.$store.dispatch("lists/toggle", list);
    }
  },
  //省略
};
</script>

//追記
<style>
li > span > span.done {
  text-decoration: line-through;
}
</style>
```