はじめに
Nuxtは他のフレームワークに比べるとNuxt自身の規則はさほど厳しくないため、同じ動作でも書き方や構成が複数存在することが多々あります。
今回まとめたものは、Nuxtの規約についてまとめたというよりも使いやすくするための一定のルールまとめなので、これがベストプラクティスというものではありません。
ひとつの基準として参考にしていただければと思います。
基本のフォルダ構成
$ npx create-nuxt-app <project-name>
で生成されたフォルダを管理しやすいように以下の構成にします。また、このフォルダ構成にするために、必ず nuxt.config.js
に srcDir: 'src/'
を指定します。
.
├──node_modules
├──src
│ ├── assets
│ │ ├── images
│ │ ├── scss
│ │ └── css
│ ├── components
│ ├── layouts
│ ├── middleware
│ ├── mixins
│ ├── pages
│ ├── plugins
│ ├── static
│ └── store
├──.editorconfig
├──.eslintignore
├──.eslintrc.js
├──.gitignore
├──nuxt.config.js
└──package.json
export default {
...,
srcDir: 'src/',
...
}
各フォルダ/ファイル解説
assets
基本的にはcss/scss/imageはこのフォルダ内で管理します。
components
各ページで共通化して利用するコンポーネント置き場。アトミックデザインを導入する場合をはじめ、ここはチームの方針によって、下位層のフォルダ構成が大きく変わってきます。
コンポーネント管理のベストプラクティスって何が良いんでしょうね、、、。未だわからず。
layouts
各ページで利用するレイアウト置き場。小規模サイトであれば、defalut.vueのみ利用することが多いと思います。レイアウトを複数使用する場合は、defalut.vueはtopページではなく一番良く使うレイアウトにすると各ページでlayoutの指定する回数を減らせたりします。
ここもどういった場合にlayoutを分け、どこまで共通化するか地味に悩んだりします。
middleware
指定したページでasyncDataやfetchよりも先に呼ばれる共通処理の置き場。nuxt.cofig.jsで全ページで指定するケースもあれば、必要なページからのみ呼び出して使うこともあります。
mixins
定数置き場。mixinsの乱用は全体の見通しを悪くさせるため、定数置き場として統一することをおすすめします。
【よく使うmixinsの使い方参考】
Vue.jsでグローバルな定数をコンポーネントで使いまわせるようにしたい
nuxt.js(v2)でSEOに必要なmeta(OGP)を入れたい
pages
各ページ置き場。layoutにこのpageが読み込まれて、各ページが生成されます。
plugins
Vueインスタンスの設定を行うファイル置き場。ここに置いたファイルをnuxt.config.jsで読み込む、というのがよくある流れです。
static
外部から読み込まれるfavicon.icoなど、外部から読み込まれる可能性があるファイル置き場。
store
コンポーネント間の値の管理。小規模なプロジェクトであれば、index.jsの1つのファイルで十分かと思います。
途中からindex.jsのファイルを複数に分けることは可能ですが、地味に手間なので、storeをガンガン使う予定のサイトの場合は、初めからある程度分けて始めることをおすすめします。
命名ルール
ファイル名
- Componentsはアッパーキャメルケース
QuestionCard.vue
- 動的ファイルはアンダーバーから始める
_id.vue
- それ以外はローワーキャメルケース
questionCard.vue
ファイル内
関数名、変数等
- 基本的にはローワーキャメルケース
const questionId = 1
script内でのcomponent宣言
- アッパーキャメルケース
<script>
export default {
components: {
QuestionCard: () => import('~/components/QuestionCard.vue')
}
}
</script>
```
### template内でのcomponent
- チェインケース
```
<template>
<question-card />
</template>
```
## 定数
- すべて大文字のスネークケース
`const QUESTION_LIST = [1, 2, 3]`
## storeのmutations
- すべて大文字のスネークケース
mutation名はaction名と同一にすることが多いですが、大文字のスネークケースにすることでactionと明確な違いつけられるので管理しやすくなります。
```javascript
export const mutations = {
SET_USER(state, { user }) {
state.user = user
}
}
```
# storeルール
## stateの読み込みは必ずgetters経由
### computedで呼び出す
```javascript:store/index.js
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['user'])
}
}
```
### fetchやasyncDataで呼び出す
```javascript:store/index.js
export default {
fetch({ store }) {
// index.jsの場合
const user = store.getters.user
// questions.jsの場合
const myQuestion = store.getters['questions/myQuestion']
}
}
```
### storeのactionsから呼び出す
```javascript:store/index.js
export const actions = {
setUser({ getters }) {
const signIn = getters.signIn
}
}
```
## stateの書き込みは必ずmutations経由
```javascript:store/index.js
export const mutations = {
SET_USER(state, { user }) {
state.user = user
}
}
```
## mutationの実行はactionのみで行う
vueファイルから呼び出すこともできるが、これは徹底。
```javascript:store/index.js
export const actions = {
setUser({ commit }, { user }) {
commit('SET_USER', { user: user })
}
}
```
## action、mutationともに、第2引数はオブジェクトで渡す
これも必須事項では有りませんが、actionおよびmutationに渡す引数は、いつか2つ以上渡したくなる時が必ずきます。関数によってその書き方変えていると後々の管理が大変になるため、ここは下記のOKパターンの徹底をおすすめします。(NGパターンでも問題なく動きます。)
- OKパターン
```javascript:store/index.js
export const mutations = {
SET_USER(state, { user }) {
state.user = user
}
}
export const actions = {
setUser({ commit }, { user }) {
commit('SET_USER', { user: user })
}
}
```
```javascript:pages/test.vue
method: {
setUserData() {
const userData = {
id: 1
name: 'tanaka'
age: 20
}
this.setUser({ user: userData })
}
},
...mapActions(['setUser']),
```
- NGパターン
```javascript:store/index.js
export const mutations = {
SET_USER(state, user) {
state.user = user
}
}
export const actions = {
setUser({ commit }, user) {
commit('SET_USER', user )
}
}
```
```javascript:pages/test.vue
method: {
setUserData() {
const userData = {
id: 1
name: 'tanaka'
age: 20
}
this.setUser(userData)
}
},
...mapActions(['setUser']),
```
## DBからのデータ取得
- 原則、action内で行う
地味に迷子になりやすかったりする部分なので、DBからのデータをstoreに保存する場合は、actionで行うことを原則とすると管理しやすいです。
```javascript:store/index.js
import axios from "axios";
export const actions = {
setUser({ commit }, { user }) {
const _user = await axios.$get('https://xxxx/xxx') // APIでDBからデータ取得
commit('SET_USER', { user: _user })
}
}
```
# asyncDataとfetchとmoutedと他
## asyncData
templateからアクセスする値をDOM生成前に入れたい場合に使用
```javascript
async asyncData({ store, route }) {
let _user = await store.getters['user']
// ユーザー情報がない場合は再取得
if (!user) {
_user = await store.dispatch('getUser', {
id: route.params.id
})
}
return {
user: _user
}
}
```
## fetch
- storeに値をDOM生成前に入れたい場合に使用
- リロードのstoreのstate消える問題の対策
```javascript
async fetch({ store, route }) {
// storeにデータがない場合のみ、再取得
if (store.getters['posts'].length === 0) {
await store.dispatch('setPosts', {
userId: route.params.id
})
}
}
```
## mounted
- クライアント側だけで実行させたい
- formの初期値代入に使用
```javascript
data() {
return {
formData: {
name: '',
age: ''
},
}
},
mounted() {
this.formData.name = this.user.name
this.formData.age = this.user.age
},
computed: {
...mapGetters(['user'])
}
```
## 他(beforeCreate, created, beforeMount, beforeUpdate, updated)
それぞれのベストな使い方については未検証です。大体、appAsync、fetch、mountedの3つでなんと買っています。
▼参考
[Vueのライフサイクルを完全に理解した](https://qiita.com/chan_kaku/items/7f3233053b0e209ef355)
# その他
## 定数はmixinsにまとめる
```javascript:mixins/moneyList.js
const moneyList = [1000, 2000, 3000, 4000, 5000]
export default {
data() {
return {
moneyList: moneyList
}
}
}
```
## Vuetifyのv-formのバリデーションルールはmixinsでまとめる
```
const validRules = {
isSelected: [v => !!v || '選択してください'],
isNotEmpty: [v => !!v || '入力してください'],
isChecked: [v => v === [] || '1つは必ず選択してください']
}
export default {
data() {
return {
validRules: validRules
}
}
}
```
## 繰り返し利用するfilterはpluginsで共通化
```javascript:mixins/filters.js
import Vue from 'vue'
import moment from '~/plugins/moment'
// 日時のフォ-マット変更1
Vue.filter('timeYMDHms', val => {
if (val) {
return moment(val).format('YY/MM/DD HH:mm:ss')
} else {
return '-'
}
})
// 日時のフォ-マット変更2
Vue.filter('timeYMD', val => {
if (val) {
return moment(val).format('YY/MM/DD')
} else {
return '-'
}
})
```
# おわりに
Nuxtをより効率的に管理するためのおすすめルールが有りましたら、ぜひコメントお願いします。